@truedat/dd 6.0.1 → 6.0.3
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/package.json +6 -6
- package/src/api.js +2 -0
- package/src/components/BucketView.js +4 -3
- package/src/components/CatalogCustomViewCards.js +4 -1
- package/src/components/FilteredNav.js +92 -37
- package/src/components/StructureCrumbs.js +10 -8
- package/src/components/StructureItem.js +43 -6
- package/src/components/StructureItemRoot.js +6 -6
- package/src/components/StructureItems.js +6 -2
- package/src/components/StructureNav.js +10 -1
- package/src/components/StructureNavClassified.js +8 -1
- package/src/components/StructureNotesEdit.js +13 -5
- package/src/components/StructureSearch.js +16 -7
- package/src/components/StructureView.js +1 -1
- package/src/components/StructuresView.js +5 -5
- package/src/components/SystemStructures.js +7 -2
- package/src/components/__tests__/BucketView.spec.js +2 -1
- package/src/components/__tests__/FilteredNav.spec.js +76 -49
- package/src/components/__tests__/StructureItems.spec.js +2 -2
- package/src/components/__tests__/StructureNav.spec.js +1 -1
- package/src/components/__tests__/StructureNavClassified.spec.js +5 -1
- package/src/components/__tests__/StructureStructureLinks.spec.js +8 -7
- package/src/components/__tests__/StructureView.spec.js +1 -0
- package/src/components/__tests__/SystemFilteredNav.spec.js +4 -1
- package/src/hooks/useBucketPaths.js +11 -0
- package/src/hooks/useBucketStructures.js +17 -5
- package/src/reducers/navFilter.js +4 -1
- package/src/reducers/structure.js +1 -0
- package/src/reducers/structureFilters.js +1 -0
- package/src/selectors/getStructureParent.js +1 -2
- package/src/components/StructureNoteSuggestions.js +0 -179
- package/src/components/__tests__/StructureNoteSuggestions.spec.js +0 -151
- package/src/components/__tests__/StructureSearch.spec.js +0 -34
- package/src/components/__tests__/__snapshots__/StructureNoteSuggestions.spec.js.snap +0 -316
- package/src/components/__tests__/__snapshots__/StructureSearch.spec.js.snap +0 -18
- package/src/utils/bucketNav.js +0 -9
|
@@ -34,7 +34,7 @@ export const StructureView = ({
|
|
|
34
34
|
<Grid.Row stretched>
|
|
35
35
|
<Grid.Column width={4}>
|
|
36
36
|
<Segment id="filter-nav-container">
|
|
37
|
-
<FilteredNav
|
|
37
|
+
<FilteredNav navFilter={navFilter} structure={structure} />
|
|
38
38
|
</Segment>
|
|
39
39
|
</Grid.Column>
|
|
40
40
|
<Grid.Column width={showGrantRequestCart ? 8 : 12}>
|
|
@@ -19,6 +19,9 @@ import CatalogCustomViewCards from "./CatalogCustomViewCards";
|
|
|
19
19
|
|
|
20
20
|
const StructuresHeader = () => {
|
|
21
21
|
const match = useRouteMatch(BUCKETS_VIEW);
|
|
22
|
+
const propertyPathMessage = !match?.params?.propertyPath
|
|
23
|
+
? "structures.subheader"
|
|
24
|
+
: match?.params?.propertyPath;
|
|
22
25
|
|
|
23
26
|
return (
|
|
24
27
|
<Header as="h2">
|
|
@@ -33,11 +36,8 @@ const StructuresHeader = () => {
|
|
|
33
36
|
/>
|
|
34
37
|
<Header.Subheader>
|
|
35
38
|
<FormattedMessage
|
|
36
|
-
id={
|
|
37
|
-
|
|
38
|
-
? "structures.subheader"
|
|
39
|
-
: match?.params?.propertyPath
|
|
40
|
-
}
|
|
39
|
+
id={propertyPathMessage}
|
|
40
|
+
defaultMessage={propertyPathMessage}
|
|
41
41
|
/>
|
|
42
42
|
</Header.Subheader>
|
|
43
43
|
</Header.Content>
|
|
@@ -21,7 +21,10 @@ export const SystemStructures = ({
|
|
|
21
21
|
const { id: systemId } = useParams();
|
|
22
22
|
|
|
23
23
|
useEffect(() => {
|
|
24
|
-
saveNavFilter({
|
|
24
|
+
saveNavFilter({
|
|
25
|
+
view: "SYSTEM_STRUCTURES",
|
|
26
|
+
filter: { system_id: systemId },
|
|
27
|
+
});
|
|
25
28
|
}, [systemId, saveNavFilter]);
|
|
26
29
|
|
|
27
30
|
return (
|
|
@@ -35,7 +38,9 @@ export const SystemStructures = ({
|
|
|
35
38
|
<Grid.Row stretched>
|
|
36
39
|
<Grid.Column width={4}>
|
|
37
40
|
<Segment>
|
|
38
|
-
{navFilter?.system_id ?
|
|
41
|
+
{navFilter?.filter?.system_id ? (
|
|
42
|
+
<FilteredNav navFilter={navFilter} />
|
|
43
|
+
) : null}
|
|
39
44
|
</Segment>
|
|
40
45
|
</Grid.Column>
|
|
41
46
|
<Grid.Column width={12}>
|
|
@@ -1,112 +1,139 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import userEvent from "@testing-library/user-event";
|
|
3
3
|
import { render } from "@truedat/test/render";
|
|
4
|
+
import { act } from "react-dom/test-utils";
|
|
5
|
+
import en from "../../messages/en";
|
|
4
6
|
|
|
5
7
|
import FilteredNavLoader, { FilteredNav } from "../FilteredNav";
|
|
6
8
|
|
|
7
9
|
jest.mock("../../hooks/useBucketStructures", () => {
|
|
8
10
|
const originalModule = jest.requireActual("../../hooks/useBucketStructures");
|
|
11
|
+
const _ = require("lodash/fp");
|
|
12
|
+
const { lowerDeburrTrim } = require("@truedat/core/services/sort");
|
|
9
13
|
|
|
10
14
|
const data = [
|
|
11
15
|
{ id: 1, name: "bank_capital" },
|
|
12
16
|
{ id: 2, name: "consumorenovables" },
|
|
13
17
|
];
|
|
14
18
|
|
|
19
|
+
const elasticSearchMock = (collection, searchValue) => {
|
|
20
|
+
const normalizedSearchValue = lowerDeburrTrim(searchValue);
|
|
21
|
+
return _.filter((structure) =>
|
|
22
|
+
_.contains(normalizedSearchValue)(lowerDeburrTrim(structure.name))
|
|
23
|
+
)(collection);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
__esModule: true,
|
|
28
|
+
...originalModule,
|
|
29
|
+
useBucketStructures: jest.fn(({ query }) => {
|
|
30
|
+
const elasticSearchResult = elasticSearchMock(data, query);
|
|
31
|
+
return {
|
|
32
|
+
data: {
|
|
33
|
+
data: { data: elasticSearchResult },
|
|
34
|
+
headers: { "x-total-count": elasticSearchResult.length },
|
|
35
|
+
},
|
|
36
|
+
error: false,
|
|
37
|
+
isLoading: false,
|
|
38
|
+
};
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
jest.mock("../../hooks/useBucketPaths", () => {
|
|
44
|
+
const originalModule = jest.requireActual("../../hooks/useBucketPaths");
|
|
45
|
+
|
|
46
|
+
const data = {
|
|
47
|
+
forest: {},
|
|
48
|
+
filtered_children: {},
|
|
49
|
+
};
|
|
50
|
+
|
|
15
51
|
return {
|
|
16
52
|
__esModule: true,
|
|
17
53
|
...originalModule,
|
|
18
|
-
|
|
19
|
-
data: { data
|
|
54
|
+
useBucketPaths: jest.fn(() => ({
|
|
55
|
+
data: { data, headers: { "x-total-count": 2 } },
|
|
20
56
|
error: false,
|
|
21
|
-
|
|
57
|
+
isLoading: false,
|
|
22
58
|
})),
|
|
23
59
|
};
|
|
24
60
|
});
|
|
25
61
|
|
|
62
|
+
const navFilter = { view: "SYSTEM_STRUCTURES", filter: {} };
|
|
63
|
+
|
|
26
64
|
const renderOpts = {
|
|
27
65
|
state: {
|
|
28
|
-
navFilter
|
|
66
|
+
navFilter,
|
|
67
|
+
},
|
|
68
|
+
messages: {
|
|
69
|
+
en: {
|
|
70
|
+
...en,
|
|
71
|
+
"structures.props.functional_name": "structures.props.functional_name",
|
|
72
|
+
},
|
|
29
73
|
},
|
|
30
74
|
};
|
|
31
75
|
|
|
32
76
|
describe("<FilteredNav />", () => {
|
|
33
|
-
it("
|
|
34
|
-
const
|
|
35
|
-
{
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const parentStructures = [];
|
|
39
|
-
const props = { childStructures, parentStructures };
|
|
40
|
-
|
|
41
|
-
const { getByText } = render(<FilteredNav {...props} />, renderOpts);
|
|
77
|
+
it("FilterNavLoader loads from useBucketStructures and shows structures", () => {
|
|
78
|
+
const { getByText } = render(
|
|
79
|
+
<FilteredNavLoader navFilter={navFilter} />,
|
|
80
|
+
renderOpts
|
|
81
|
+
);
|
|
42
82
|
expect(getByText("bank_capital")).toBeInTheDocument();
|
|
43
83
|
expect(getByText("consumorenovables")).toBeInTheDocument();
|
|
44
84
|
});
|
|
45
85
|
|
|
46
|
-
it("
|
|
47
|
-
const childStructures = [
|
|
48
|
-
{ id: 1, name: "bank_capital" },
|
|
49
|
-
{ id: 2, name: "consumorenovables" },
|
|
50
|
-
];
|
|
51
|
-
const parentStructures = [];
|
|
52
|
-
const props = { childStructures, parentStructures };
|
|
53
|
-
|
|
86
|
+
it("StructureSearch: one element matching", async () => {
|
|
54
87
|
const { getByRole, getByText, queryByText } = render(
|
|
55
|
-
<
|
|
88
|
+
<FilteredNavLoader navFilter={navFilter} />,
|
|
56
89
|
renderOpts
|
|
57
90
|
);
|
|
58
91
|
expect(getByText("bank_capital")).toBeInTheDocument();
|
|
59
92
|
expect(getByText("consumorenovables")).toBeInTheDocument();
|
|
60
93
|
|
|
94
|
+
jest.useFakeTimers();
|
|
61
95
|
const inputSearch = getByRole("textbox");
|
|
62
96
|
userEvent.type(inputSearch, "bank");
|
|
97
|
+
act(() => jest.runAllTimers());
|
|
63
98
|
expect(getByText("bank_capital")).toBeInTheDocument();
|
|
64
99
|
expect(queryByText("consumorenovables")).not.toBeInTheDocument();
|
|
65
100
|
});
|
|
66
101
|
|
|
67
|
-
it("
|
|
68
|
-
const childStructures = [
|
|
69
|
-
{ id: 1, name: "bank_capital" },
|
|
70
|
-
{ id: 2, name: "consumorenovables" },
|
|
71
|
-
];
|
|
72
|
-
const parentStructures = [];
|
|
73
|
-
const props = { childStructures, parentStructures };
|
|
74
|
-
|
|
102
|
+
it("StructureSearch: no elements matching", () => {
|
|
75
103
|
const { getByRole, getByText, queryByText } = render(
|
|
76
|
-
<
|
|
104
|
+
<FilteredNavLoader navFilter={navFilter} />,
|
|
77
105
|
renderOpts
|
|
78
106
|
);
|
|
79
107
|
expect(getByText("bank_capital")).toBeInTheDocument();
|
|
80
108
|
expect(getByText("consumorenovables")).toBeInTheDocument();
|
|
81
109
|
|
|
110
|
+
jest.useFakeTimers();
|
|
82
111
|
const inputSearch = getByRole("textbox");
|
|
83
112
|
userEvent.type(inputSearch, "renovabless");
|
|
84
|
-
|
|
113
|
+
act(() => jest.runAllTimers());
|
|
85
114
|
expect(queryByText("bank_capital")).not.toBeInTheDocument();
|
|
86
115
|
expect(queryByText("consumorenovables")).not.toBeInTheDocument();
|
|
87
116
|
});
|
|
88
117
|
|
|
89
|
-
it("FilterNavLoader
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
118
|
+
// it("FilterNavLoader shows displayed/total structures message", () => {
|
|
119
|
+
// const { queryByText, getByText, getByRole } = render(
|
|
120
|
+
// <FilteredNavLoader navFilter={navFilter} />,
|
|
121
|
+
// renderOpts
|
|
122
|
+
// );
|
|
94
123
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const inputSearch = getByRole("textbox");
|
|
102
|
-
expect(queryByText("1 from a total of 2")).not.toBeInTheDocument();
|
|
103
|
-
userEvent.type(inputSearch, "bank");
|
|
104
|
-
expect(getByText("1 from a total of 2")).toBeInTheDocument();
|
|
105
|
-
});
|
|
124
|
+
// const inputSearch = getByRole("textbox");
|
|
125
|
+
// expect(queryByText("1 from a total of 2")).not.toBeInTheDocument();
|
|
126
|
+
// userEvent.type(inputSearch, "bank");
|
|
127
|
+
// expect(getByText("1 from a total of 2")).toBeInTheDocument();
|
|
128
|
+
// });
|
|
106
129
|
|
|
107
130
|
it("test Alias Name Mode functionality", async () => {
|
|
131
|
+
const childStructures = [
|
|
132
|
+
{ id: 1, name: "bank_capital" },
|
|
133
|
+
{ id: 2, name: "consumorenovables" },
|
|
134
|
+
];
|
|
108
135
|
const toggleStructuresAliasNameMode = jest.fn();
|
|
109
|
-
const props = { toggleStructuresAliasNameMode };
|
|
136
|
+
const props = { toggleStructuresAliasNameMode, childStructures };
|
|
110
137
|
const { getByRole } = render(<FilteredNav {...props} />, renderOpts);
|
|
111
138
|
|
|
112
139
|
userEvent.click(getByRole("button", { name: /enable/i }));
|
|
@@ -5,7 +5,7 @@ import { StructureItems } from "../StructureItems";
|
|
|
5
5
|
describe("<StructureItems />", () => {
|
|
6
6
|
it("matches the latest snapshot", () => {
|
|
7
7
|
const structures = [{ id: 1, name: "some structure" }];
|
|
8
|
-
const props = { structures };
|
|
8
|
+
const props = { structures, fnHasCatalogViewProp: jest.fn() };
|
|
9
9
|
|
|
10
10
|
const { container } = render(<StructureItems {...props} />);
|
|
11
11
|
expect(container).toMatchSnapshot();
|
|
@@ -13,7 +13,7 @@ describe("<StructureItems />", () => {
|
|
|
13
13
|
|
|
14
14
|
it("matches the latest snapshot with alias", () => {
|
|
15
15
|
const structures = [{ id: 1, name: "some structure", alias: "alias" }];
|
|
16
|
-
const props = { structures };
|
|
16
|
+
const props = { structures, fnHasCatalogViewProp: jest.fn() };
|
|
17
17
|
|
|
18
18
|
const { container } = render(<StructureItems {...props} />);
|
|
19
19
|
expect(container).toMatchSnapshot();
|
|
@@ -17,7 +17,7 @@ const renderOpts = {
|
|
|
17
17
|
describe("<StructureNav />", () => {
|
|
18
18
|
it("matches the latest snapshot", () => {
|
|
19
19
|
const childStructures = [{ id: 1 }];
|
|
20
|
-
const props = { childStructures };
|
|
20
|
+
const props = { childStructures, fnHasCatalogViewProp: jest.fn() };
|
|
21
21
|
|
|
22
22
|
const { container } = render(<StructureNav {...props} />, renderOpts);
|
|
23
23
|
expect(container).toMatchSnapshot();
|
|
@@ -6,7 +6,11 @@ const structure1 = { id: 1, classes: { foo: "bar" } };
|
|
|
6
6
|
const structure2 = { id: 2, classes: null };
|
|
7
7
|
const structures = [structure1, structure2];
|
|
8
8
|
|
|
9
|
-
const props = {
|
|
9
|
+
const props = {
|
|
10
|
+
classifier: "foo",
|
|
11
|
+
structures,
|
|
12
|
+
fnHasCatalogViewProp: jest.fn(),
|
|
13
|
+
};
|
|
10
14
|
|
|
11
15
|
describe("<StructureNavClassified />", () => {
|
|
12
16
|
it("matches the latest snapshot", () => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
1
2
|
import React from "react";
|
|
2
3
|
import { render } from "@truedat/test/render";
|
|
3
4
|
import { waitFor } from "@testing-library/react";
|
|
@@ -161,13 +162,14 @@ describe("<StructureStructureLinks />", () => {
|
|
|
161
162
|
const renderOpts = {
|
|
162
163
|
routes: ["/structures/60217/structureLinks"],
|
|
163
164
|
mocks: [linksMock],
|
|
165
|
+
state: { navFilter: { view: "SYSTEM_STRUCTURES", filter: {} } },
|
|
164
166
|
};
|
|
165
167
|
|
|
166
168
|
it("shows structure to structure links", async () => {
|
|
167
|
-
const { getByRole } = render(
|
|
168
|
-
...
|
|
169
|
-
state: { structureActions }
|
|
170
|
-
|
|
169
|
+
const { getByRole } = render(
|
|
170
|
+
<StructureStructureLinks {...props} />,
|
|
171
|
+
_.merge(renderOpts, { state: { structureActions } })
|
|
172
|
+
);
|
|
171
173
|
await waitFor(() => {
|
|
172
174
|
expect(getByRole("link", { name: /STRUCTURE_2/i })).toBeInTheDocument();
|
|
173
175
|
expect(getByRole("link", { name: /STRUCTURE_3/i })).toBeInTheDocument();
|
|
@@ -203,11 +205,10 @@ describe("<StructureStructureLinks />", () => {
|
|
|
203
205
|
const dispatch = jest.fn();
|
|
204
206
|
const { getByText, getByRole } = render(
|
|
205
207
|
<StructureStructureLinks {...props} />,
|
|
206
|
-
{
|
|
207
|
-
...renderOpts,
|
|
208
|
+
_.merge(renderOpts, {
|
|
208
209
|
state: { structureActions },
|
|
209
210
|
dispatch,
|
|
210
|
-
}
|
|
211
|
+
})
|
|
211
212
|
);
|
|
212
213
|
await waitFor(() => {
|
|
213
214
|
expect(getByRole("link", { name: /STRUCTURE_2/i })).toBeInTheDocument();
|
|
@@ -26,7 +26,10 @@ const renderOpts = {
|
|
|
26
26
|
|
|
27
27
|
describe("<SystemFilteredNav />", () => {
|
|
28
28
|
it("matches the latest snapshot", () => {
|
|
29
|
-
const { container } = render(
|
|
29
|
+
const { container } = render(
|
|
30
|
+
<SystemFilteredNav fnHasCatalogViewProp={jest.fn()} />,
|
|
31
|
+
renderOpts
|
|
32
|
+
);
|
|
30
33
|
expect(container).toMatchSnapshot();
|
|
31
34
|
});
|
|
32
35
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import useSWR from "swr";
|
|
2
|
+
import { apiJsonPost } from "@truedat/core/services/api";
|
|
3
|
+
import { API_BUCKET_PATHS } from "../api";
|
|
4
|
+
|
|
5
|
+
export const useBucketPaths = (navFilter) =>
|
|
6
|
+
useSWR(
|
|
7
|
+
navFilter.view === "BUCKET_VIEW"
|
|
8
|
+
? { url: API_BUCKET_PATHS, filters: navFilter.filter }
|
|
9
|
+
: null,
|
|
10
|
+
({ url }) => apiJsonPost(url, { filters: navFilter.filter })
|
|
11
|
+
);
|
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
import
|
|
1
|
+
import useSWR from "swr";
|
|
2
2
|
import { apiJsonPost } from "@truedat/core/services/api";
|
|
3
3
|
import { API_BUCKET_STRUCTURES } from "../api";
|
|
4
4
|
|
|
5
|
-
export const useBucketStructures = (
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export const useBucketStructures = ({
|
|
6
|
+
navFilter,
|
|
7
|
+
parentFilter,
|
|
8
|
+
ids,
|
|
9
|
+
query,
|
|
10
|
+
}) => {
|
|
11
|
+
const params =
|
|
12
|
+
navFilter?.view === "SYSTEM_STRUCTURES"
|
|
13
|
+
? { filters: { ...navFilter.filter, ...parentFilter }, query }
|
|
14
|
+
: // BUCKET_VIEW searches by ids directly instead of navFilter and parentFilter
|
|
15
|
+
navFilter?.view === "BUCKET_VIEW"
|
|
16
|
+
? { filters: { ids }, query }
|
|
17
|
+
: undefined;
|
|
18
|
+
|
|
19
|
+
return useSWR({ url: API_BUCKET_STRUCTURES, params }, ({ url }) =>
|
|
20
|
+
apiJsonPost(url, params)
|
|
8
21
|
);
|
|
9
|
-
return mutation;
|
|
10
22
|
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { clearNavFilter, saveNavFilter } from "../routines";
|
|
2
2
|
|
|
3
|
-
export const initialState = {
|
|
3
|
+
export const initialState = {
|
|
4
|
+
view: "SYSTEM_STRUCTURES",
|
|
5
|
+
filter: {},
|
|
6
|
+
};
|
|
4
7
|
|
|
5
8
|
export const navFilter = (state = initialState, { type, payload }) => {
|
|
6
9
|
switch (type) {
|
|
@@ -6,6 +6,7 @@ const initialState = {};
|
|
|
6
6
|
const structureFilters = (state = initialState, { type, payload }) => {
|
|
7
7
|
switch (type) {
|
|
8
8
|
case clearStructureFilters.TRIGGER:
|
|
9
|
+
case fetchStructureFilters.TRIGGER:
|
|
9
10
|
return initialState;
|
|
10
11
|
case fetchStructureFilters.SUCCESS:
|
|
11
12
|
return _.flow(
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
2
|
import { createSelector } from "reselect";
|
|
3
|
-
import { isBucketFilter } from "../utils/bucketNav";
|
|
4
3
|
|
|
5
4
|
const defaultName = "..";
|
|
6
5
|
|
|
@@ -23,7 +22,7 @@ export const getStructureParent = createSelector(
|
|
|
23
22
|
],
|
|
24
23
|
(structure, structureParents, navFilter) =>
|
|
25
24
|
_.isEmpty(structureParents)
|
|
26
|
-
? !_.isEmpty(structure) &&
|
|
25
|
+
? !_.isEmpty(structure) && navFilter?.view === "BUCKET_VIEW"
|
|
27
26
|
? { type: "bucket" }
|
|
28
27
|
: _.has("system")(structure)
|
|
29
28
|
? rootNavItem(structure)
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import _ from "lodash/fp";
|
|
2
|
-
import React, { useState } from "react";
|
|
3
|
-
import PropTypes from "prop-types";
|
|
4
|
-
import {
|
|
5
|
-
Button,
|
|
6
|
-
Checkbox,
|
|
7
|
-
Container,
|
|
8
|
-
Divider,
|
|
9
|
-
Icon,
|
|
10
|
-
List,
|
|
11
|
-
Message,
|
|
12
|
-
Segment,
|
|
13
|
-
} from "semantic-ui-react";
|
|
14
|
-
import { FormattedMessage, useIntl } from "react-intl";
|
|
15
|
-
import { apiJson } from "@truedat/core/services/api";
|
|
16
|
-
|
|
17
|
-
export default function StructureNoteSuggestions({
|
|
18
|
-
aiSuggestionsAction,
|
|
19
|
-
applySuggestions,
|
|
20
|
-
template,
|
|
21
|
-
isModification,
|
|
22
|
-
}) {
|
|
23
|
-
const [loadingSuggestion, setLoadingSuggestion] = useState();
|
|
24
|
-
const [suggestions, setSuggestions] = useState();
|
|
25
|
-
const [suggestionsError, setSuggestionsError] = useState();
|
|
26
|
-
const [selectedSuggestions, setSelectedSuggestions] = useState([]);
|
|
27
|
-
|
|
28
|
-
const { locale } = useIntl();
|
|
29
|
-
|
|
30
|
-
const suggestionKeys = _.keys(suggestions);
|
|
31
|
-
const fieldInSuggestions = ({ name }) => _.includes(name)(suggestionKeys);
|
|
32
|
-
const templateFields = _.flow(
|
|
33
|
-
_.prop("content"),
|
|
34
|
-
_.filter(({ fields }) => _.any(fieldInSuggestions)(fields)),
|
|
35
|
-
_.map(({ name, fields }) => ({
|
|
36
|
-
name,
|
|
37
|
-
fields: _.filter(fieldInSuggestions)(fields),
|
|
38
|
-
}))
|
|
39
|
-
)(template);
|
|
40
|
-
const validFields = _.flow(
|
|
41
|
-
_.prop("content"),
|
|
42
|
-
_.flatMap(({ fields }) =>
|
|
43
|
-
_.flow(
|
|
44
|
-
_.filter(({ editable }) => editable || !isModification),
|
|
45
|
-
_.map(({ name }) => name)
|
|
46
|
-
)(fields)
|
|
47
|
-
)
|
|
48
|
-
)(template);
|
|
49
|
-
|
|
50
|
-
const handleRequestSuggestions = () => {
|
|
51
|
-
setLoadingSuggestion(true);
|
|
52
|
-
setSuggestionsError(null);
|
|
53
|
-
setSelectedSuggestions([]);
|
|
54
|
-
setSuggestions(null);
|
|
55
|
-
const url = `${aiSuggestionsAction.href}?language=${locale}`;
|
|
56
|
-
apiJson(url).then(({ data: { data: suggestions } }) => {
|
|
57
|
-
if (_.prop("[0]")(suggestions) === "error") {
|
|
58
|
-
setSuggestionsError(
|
|
59
|
-
_.prop("[1].error.message")(suggestions) || _.prop("[1]")(suggestions)
|
|
60
|
-
);
|
|
61
|
-
} else {
|
|
62
|
-
setSuggestions(suggestions);
|
|
63
|
-
const selectedFields = _.flow(
|
|
64
|
-
_.keys,
|
|
65
|
-
_.filter(
|
|
66
|
-
(key) =>
|
|
67
|
-
_.includes(key)(validFields) && !_.isEmpty(suggestions[key])
|
|
68
|
-
)
|
|
69
|
-
)(suggestions);
|
|
70
|
-
setSelectedSuggestions(selectedFields);
|
|
71
|
-
}
|
|
72
|
-
setLoadingSuggestion(false);
|
|
73
|
-
});
|
|
74
|
-
};
|
|
75
|
-
const handleApplySuggestions = () => {
|
|
76
|
-
applySuggestions(_.pick(selectedSuggestions)(suggestions));
|
|
77
|
-
setSelectedSuggestions([]);
|
|
78
|
-
setSuggestions(null);
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const toggleSelectedSuggestion = (name) => {
|
|
82
|
-
setSelectedSuggestions(
|
|
83
|
-
_.includes(name)(selectedSuggestions)
|
|
84
|
-
? _.reject((v) => v == name)(selectedSuggestions)
|
|
85
|
-
: [...selectedSuggestions, name]
|
|
86
|
-
);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
return (
|
|
90
|
-
<Segment loading={loadingSuggestion}>
|
|
91
|
-
<Container style={{ display: "flex", justifyContent: "space-between" }}>
|
|
92
|
-
<h4>
|
|
93
|
-
{suggestions ? (
|
|
94
|
-
<Icon name="lightbulb outline" />
|
|
95
|
-
) : (
|
|
96
|
-
<Icon name="lightbulb" />
|
|
97
|
-
)}
|
|
98
|
-
<FormattedMessage id="structure_note.ai_suggestion.header" />
|
|
99
|
-
</h4>
|
|
100
|
-
<Divider hidden />
|
|
101
|
-
{suggestions ? null : (
|
|
102
|
-
<Button onClick={handleRequestSuggestions}>
|
|
103
|
-
<FormattedMessage id="actions.ai_suggestion" />
|
|
104
|
-
</Button>
|
|
105
|
-
)}
|
|
106
|
-
</Container>
|
|
107
|
-
{suggestionsError ? (
|
|
108
|
-
<Message negative>
|
|
109
|
-
<Message.Header>
|
|
110
|
-
<FormattedMessage id="structure_note.ai_suggestion.error" />
|
|
111
|
-
</Message.Header>
|
|
112
|
-
<p>{suggestionsError}</p>
|
|
113
|
-
</Message>
|
|
114
|
-
) : null}
|
|
115
|
-
{suggestions ? (
|
|
116
|
-
<>
|
|
117
|
-
{_.map(({ name: groupName, fields }) => (
|
|
118
|
-
<Container key={`group-${groupName}`}>
|
|
119
|
-
<>
|
|
120
|
-
<h5>{groupName ? groupName : ""}</h5>
|
|
121
|
-
<List>
|
|
122
|
-
{_.map(({ name, label, editable }) => (
|
|
123
|
-
<List.Item
|
|
124
|
-
key={name}
|
|
125
|
-
style={{
|
|
126
|
-
display: "flex",
|
|
127
|
-
alignItems: "center",
|
|
128
|
-
gap: "12px",
|
|
129
|
-
}}
|
|
130
|
-
>
|
|
131
|
-
<Checkbox
|
|
132
|
-
id={name}
|
|
133
|
-
disabled={
|
|
134
|
-
(isModification && !editable) ||
|
|
135
|
-
_.isEmpty(suggestions[name])
|
|
136
|
-
}
|
|
137
|
-
checked={_.includes(name)(selectedSuggestions)}
|
|
138
|
-
onChange={() => toggleSelectedSuggestion(name)}
|
|
139
|
-
/>
|
|
140
|
-
<label
|
|
141
|
-
htmlFor={name}
|
|
142
|
-
style={{ cursor: "pointer" }}
|
|
143
|
-
disabled
|
|
144
|
-
>
|
|
145
|
-
<List.Header>{label}</List.Header>
|
|
146
|
-
<List.Description>
|
|
147
|
-
{_.isEmpty(suggestions[name])
|
|
148
|
-
? "-"
|
|
149
|
-
: suggestions[name]}
|
|
150
|
-
</List.Description>
|
|
151
|
-
</label>
|
|
152
|
-
</List.Item>
|
|
153
|
-
))(fields)}
|
|
154
|
-
</List>
|
|
155
|
-
<Divider hidden />
|
|
156
|
-
</>
|
|
157
|
-
</Container>
|
|
158
|
-
))(templateFields)}
|
|
159
|
-
<Container textAlign="right">
|
|
160
|
-
<Button
|
|
161
|
-
primary
|
|
162
|
-
onClick={handleApplySuggestions}
|
|
163
|
-
disabled={_.isEmpty(selectedSuggestions)}
|
|
164
|
-
>
|
|
165
|
-
<FormattedMessage id="actions.apply_ai_suggestion" />
|
|
166
|
-
</Button>
|
|
167
|
-
</Container>
|
|
168
|
-
</>
|
|
169
|
-
) : null}
|
|
170
|
-
</Segment>
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
StructureNoteSuggestions.propTypes = {
|
|
175
|
-
aiSuggestionsAction: PropTypes.object,
|
|
176
|
-
applySuggestions: PropTypes.func,
|
|
177
|
-
template: PropTypes.object,
|
|
178
|
-
isModification: PropTypes.bool,
|
|
179
|
-
};
|