@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.
Files changed (36) hide show
  1. package/package.json +6 -6
  2. package/src/api.js +2 -0
  3. package/src/components/BucketView.js +4 -3
  4. package/src/components/CatalogCustomViewCards.js +4 -1
  5. package/src/components/FilteredNav.js +92 -37
  6. package/src/components/StructureCrumbs.js +10 -8
  7. package/src/components/StructureItem.js +43 -6
  8. package/src/components/StructureItemRoot.js +6 -6
  9. package/src/components/StructureItems.js +6 -2
  10. package/src/components/StructureNav.js +10 -1
  11. package/src/components/StructureNavClassified.js +8 -1
  12. package/src/components/StructureNotesEdit.js +13 -5
  13. package/src/components/StructureSearch.js +16 -7
  14. package/src/components/StructureView.js +1 -1
  15. package/src/components/StructuresView.js +5 -5
  16. package/src/components/SystemStructures.js +7 -2
  17. package/src/components/__tests__/BucketView.spec.js +2 -1
  18. package/src/components/__tests__/FilteredNav.spec.js +76 -49
  19. package/src/components/__tests__/StructureItems.spec.js +2 -2
  20. package/src/components/__tests__/StructureNav.spec.js +1 -1
  21. package/src/components/__tests__/StructureNavClassified.spec.js +5 -1
  22. package/src/components/__tests__/StructureStructureLinks.spec.js +8 -7
  23. package/src/components/__tests__/StructureView.spec.js +1 -0
  24. package/src/components/__tests__/SystemFilteredNav.spec.js +4 -1
  25. package/src/hooks/useBucketPaths.js +11 -0
  26. package/src/hooks/useBucketStructures.js +17 -5
  27. package/src/reducers/navFilter.js +4 -1
  28. package/src/reducers/structure.js +1 -0
  29. package/src/reducers/structureFilters.js +1 -0
  30. package/src/selectors/getStructureParent.js +1 -2
  31. package/src/components/StructureNoteSuggestions.js +0 -179
  32. package/src/components/__tests__/StructureNoteSuggestions.spec.js +0 -151
  33. package/src/components/__tests__/StructureSearch.spec.js +0 -34
  34. package/src/components/__tests__/__snapshots__/StructureNoteSuggestions.spec.js.snap +0 -316
  35. package/src/components/__tests__/__snapshots__/StructureSearch.spec.js.snap +0 -18
  36. 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 filter={navFilter} structure={structure} />
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
- !match?.params?.propertyPath
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({ system_id: systemId });
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 ? <FilteredNav filter={navFilter} /> : null}
41
+ {navFilter?.filter?.system_id ? (
42
+ <FilteredNav navFilter={navFilter} />
43
+ ) : null}
39
44
  </Segment>
40
45
  </Grid.Column>
41
46
  <Grid.Column width={12}>
@@ -49,7 +49,8 @@ describe("<BucketView />", () => {
49
49
  * mock store from "@truedat/test/render"
50
50
  */
51
51
  expect(saveNavFilter).toHaveBeenCalledWith({
52
- "metadata.region": "eu-west-1",
52
+ view: "BUCKET_VIEW",
53
+ filter: { "metadata.region": "eu-west-1" },
53
54
  })
54
55
  );
55
56
  });
@@ -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
- useBucketStructures: jest.fn(() => ({
19
- data: { data: { data }, headers: { "x-total-count": 2 } },
54
+ useBucketPaths: jest.fn(() => ({
55
+ data: { data, headers: { "x-total-count": 2 } },
20
56
  error: false,
21
- trigger: jest.fn(),
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("matches the latest snapshot", () => {
34
- const childStructures = [
35
- { id: 1, name: "bank_capital" },
36
- { id: 2, name: "consumorenovables" },
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("ChildStructures prop in StructureNav component should rendered with one item when inputSearch value change", () => {
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
- <FilteredNav {...props} />,
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("ChildStructures prop in StructureNav component should rendered without items when inputSearch value change", () => {
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
- <FilteredNav {...props} />,
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 loads from useBucketStructures and shows structures", () => {
90
- const { getByText } = render(<FilteredNavLoader />, renderOpts);
91
- expect(getByText("bank_capital")).toBeInTheDocument();
92
- expect(getByText("consumorenovables")).toBeInTheDocument();
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
- it("FilterNavLoader shows displayed/total structures message", () => {
96
- const { queryByText, getByText, getByRole } = render(
97
- <FilteredNavLoader />,
98
- renderOpts
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 = { classifier: "foo", structures };
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(<StructureStructureLinks {...props} />, {
168
- ...renderOpts,
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();
@@ -44,6 +44,7 @@ describe("<StructureView />", () => {
44
44
  name: "some_db",
45
45
  type: "Database",
46
46
  description: "some description",
47
+ ancestry: [],
47
48
  },
48
49
  },
49
50
  };
@@ -26,7 +26,10 @@ const renderOpts = {
26
26
 
27
27
  describe("<SystemFilteredNav />", () => {
28
28
  it("matches the latest snapshot", () => {
29
- const { container } = render(<SystemFilteredNav />, renderOpts);
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 useSWRMutations from "swr/mutation";
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
- const mutation = useSWRMutations(API_BUCKET_STRUCTURES, (url, { arg }) =>
7
- apiJsonPost(url, arg)
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) {
@@ -19,6 +19,7 @@ const structureFields = (fn) =>
19
19
  );
20
20
 
21
21
  const structureVersionFields = _.pick([
22
+ "ancestry",
22
23
  "class",
23
24
  "classes",
24
25
  "data_structure_link_count",
@@ -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) && isBucketFilter(navFilter)
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
- };