@truedat/bg 8.13.4 → 8.13.6

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 (26) hide show
  1. package/package.json +3 -3
  2. package/src/concepts/api.js +5 -9
  3. package/src/concepts/components/ConceptEdit.js +23 -1
  4. package/src/concepts/components/ConceptForm.js +28 -1
  5. package/src/concepts/components/ConceptRoutes.js +10 -13
  6. package/src/concepts/components/ConceptsUploadButton.js +14 -5
  7. package/src/concepts/components/__tests__/ConceptEdit.dateRestrictions.spec.js +84 -0
  8. package/src/concepts/components/__tests__/ConceptEdit.spec.js +29 -0
  9. package/src/concepts/components/__tests__/ConceptEditLoader.spec.js +159 -0
  10. package/src/concepts/components/__tests__/ConceptForm.spec.js +97 -1
  11. package/src/concepts/components/__tests__/ConceptRoutes.spec.js +11 -6
  12. package/src/concepts/components/__tests__/__snapshots__/ConceptRoutes.spec.js.snap +8 -11
  13. package/src/concepts/hooks/useConcepts.js +9 -4
  14. package/src/concepts/sagas/__tests__/downloadConcepts.spec.js +3 -3
  15. package/src/concepts/sagas/downloadConcepts.js +2 -2
  16. package/src/concepts/selectors/__tests__/messages.spec.js +63 -0
  17. package/src/concepts/selectors/index.js +0 -4
  18. package/src/concepts/selectors/messages.js +20 -3
  19. package/src/concepts/components/ConceptsUploadEvents.js +0 -31
  20. package/src/concepts/components/ConceptsUploadEventsTable.js +0 -183
  21. package/src/concepts/components/__tests__/ConceptsUploadEventsTable.spec.js +0 -100
  22. package/src/concepts/components/__tests__/__snapshots__/ConceptsUploadEventsTable.spec.js.snap +0 -115
  23. package/src/concepts/hooks/useUploadEvents.js +0 -11
  24. package/src/concepts/selectors/__tests__/getConceptUploadEventColumns.spec.js +0 -20
  25. package/src/concepts/selectors/getConceptUploadEventColumns.js +0 -66
  26. package/src/concepts/styles/ConceptsUploadEventColumns.less +0 -24
@@ -1,7 +1,7 @@
1
1
  import { waitFor } from "@testing-library/react";
2
2
  import { render, waitForLoad } from "@truedat/test/render";
3
3
  import { domainsMock, multipleTemplatesMock } from "@truedat/test/mocks";
4
- import ConceptForm from "../ConceptForm";
4
+ import ConceptForm, { conceptHasDateRestrictionErrors } from "../ConceptForm";
5
5
 
6
6
  jest.mock("@truedat/ai/hooks/useSuggestions", () => {
7
7
  const originalModule = jest.requireActual("@truedat/ai/hooks/useSuggestions");
@@ -76,7 +76,103 @@ const props = {
76
76
  const templateVariables = { scope: "bg", domainIds: [1] };
77
77
  const domainVariables = { action: "createBusinessConcept" };
78
78
 
79
+ const dateTemplate = {
80
+ content: [
81
+ {
82
+ name: "dates",
83
+ fields: [
84
+ {
85
+ name: "fecha",
86
+ type: "date",
87
+ restrictions: { resolved: { min: null, max: "2026-10-15" } },
88
+ },
89
+ ],
90
+ },
91
+ ],
92
+ };
93
+
94
+ const savedConcept = {
95
+ en: { content: { fecha: { value: "2026-10-16", origin: "user" } } },
96
+ };
97
+
79
98
  describe("<ConceptForm />", () => {
99
+ it("allows an unchanged existing date outside the latest restriction", () => {
100
+ expect(
101
+ conceptHasDateRestrictionErrors(
102
+ dateTemplate,
103
+ {
104
+ en: {
105
+ name: "Updated name",
106
+ content: { fecha: { value: "2026-10-16", origin: "user" } },
107
+ },
108
+ },
109
+ savedConcept,
110
+ ),
111
+ ).toBe(false);
112
+ });
113
+
114
+ it("rejects a modified date outside the latest restriction", () => {
115
+ expect(
116
+ conceptHasDateRestrictionErrors(
117
+ dateTemplate,
118
+ { en: { content: { fecha: { value: "2026-10-17", origin: "user" } } } },
119
+ savedConcept,
120
+ ),
121
+ ).toBe(true);
122
+ });
123
+
124
+ it("does not exempt a new language using another language's saved date", () => {
125
+ expect(
126
+ conceptHasDateRestrictionErrors(
127
+ dateTemplate,
128
+ {
129
+ ...savedConcept,
130
+ es: { content: { fecha: { value: "2026-10-16", origin: "user" } } },
131
+ },
132
+ savedConcept,
133
+ ),
134
+ ).toBe(true);
135
+ });
136
+
137
+ it("detects an invalid date so Save can be disabled", () => {
138
+ const template = {
139
+ content: [
140
+ {
141
+ name: "dates",
142
+ fields: [
143
+ {
144
+ name: "fecha",
145
+ type: "date",
146
+ restrictions: {
147
+ resolved: { min: null, max: "2026-10-15" },
148
+ },
149
+ },
150
+ ],
151
+ },
152
+ ],
153
+ };
154
+
155
+ expect(
156
+ conceptHasDateRestrictionErrors(template, {
157
+ en: {
158
+ content: {
159
+ fecha: { value: "2026-10-16", origin: "user" },
160
+ },
161
+ },
162
+ }),
163
+ ).toBe(true);
164
+
165
+ expect(
166
+ conceptHasDateRestrictionErrors(template, {
167
+ en: {
168
+ content: {
169
+ fecha: { value: "2026-10-15", origin: "user" },
170
+ },
171
+ },
172
+ }),
173
+ ).toBe(false);
174
+ });
175
+
80
176
  describe("with multiple templates", () => {
81
177
  const renderOpts = {
82
178
  mocks: [
@@ -7,7 +7,7 @@ import {
7
7
  CONCEPTS_DEPRECATED,
8
8
  CONCEPTS_NEW,
9
9
  CONCEPTS_BULK_UPDATE,
10
- CONCEPTS_BULK_UPLOAD_EVENTS,
10
+ CONCEPTS_UPLOAD_JOBS,
11
11
  CONCEPT_LINKS_MANAGEMENT,
12
12
  CONCEPT_VERSION,
13
13
  CONCEPT_EDIT,
@@ -35,8 +35,11 @@ jest.mock("../ConceptsBulkUpdate", () => () => <div>ConceptsBulkUpdate</div>);
35
35
  jest.mock("../ConceptSubscriptionLoader", () => () => (
36
36
  <div>ConceptSubscriptionLoader</div>
37
37
  ));
38
- jest.mock("../ConceptsUploadEvents", () => () => (
39
- <div>ConceptsUploadEvents</div>
38
+ jest.mock("@truedat/core/components/UploadJobs", () => () => (
39
+ <div>UploadJobs</div>
40
+ ));
41
+ jest.mock("@truedat/core/components/UploadJob", () => () => (
42
+ <div>UploadJob</div>
40
43
  ));
41
44
  jest.mock("../../relations/components/ConceptLinksApprovals", () => () => (
42
45
  <div>ConceptLinksApprovals</div>
@@ -106,9 +109,9 @@ describe("<ConceptRoutes />", () => {
106
109
  expect(rendered.container).toMatchSnapshot();
107
110
  });
108
111
 
109
- it("renders correctly with bulk upload events route", async () => {
112
+ it("renders correctly with upload jobs route", async () => {
110
113
  const rendered = render(<ConceptRoutes />, {
111
- routes: [CONCEPTS_BULK_UPLOAD_EVENTS],
114
+ routes: [CONCEPTS_UPLOAD_JOBS],
112
115
  });
113
116
  await waitForLoad(rendered);
114
117
  expect(rendered.container).toMatchSnapshot();
@@ -149,10 +152,12 @@ describe("<ConceptRoutes />", () => {
149
152
  state: {
150
153
  concept: { business_concept_id: "123" },
151
154
  templatesLoading: false,
152
- templates: { some: "template" },
155
+ templates: [],
153
156
  },
154
157
  });
155
158
  await waitForLoad(rendered);
159
+ expect(rendered.getByText("ConceptEdit")).toBeInTheDocument();
160
+ expect(rendered.queryByText("TemplatesLoader")).not.toBeInTheDocument();
156
161
  expect(rendered.container).toMatchSnapshot();
157
162
  });
158
163
 
@@ -14,22 +14,11 @@ exports[`<ConceptRoutes /> renders correctly with bulk update route 1`] = `
14
14
  </div>
15
15
  `;
16
16
 
17
- exports[`<ConceptRoutes /> renders correctly with bulk upload events route 1`] = `
18
- <div>
19
- <div>
20
- ConceptsUploadEvents
21
- </div>
22
- </div>
23
- `;
24
-
25
17
  exports[`<ConceptRoutes /> renders correctly with concept edit route 1`] = `
26
18
  <div>
27
19
  <div>
28
20
  ConceptCrumbs
29
21
  </div>
30
- <div>
31
- TemplatesLoader
32
- </div>
33
22
  <div>
34
23
  ConceptLoader
35
24
  </div>
@@ -139,6 +128,14 @@ exports[`<ConceptRoutes /> renders correctly with subscope route 1`] = `
139
128
  </div>
140
129
  `;
141
130
 
131
+ exports[`<ConceptRoutes /> renders correctly with upload jobs route 1`] = `
132
+ <div>
133
+ <div>
134
+ UploadJobs
135
+ </div>
136
+ </div>
137
+ `;
138
+
142
139
  exports[`<ConceptRoutes /> renders unauthorized component when not authorized 1`] = `
143
140
  <div>
144
141
  <div
@@ -1,7 +1,12 @@
1
1
  import useSWR from "swr";
2
2
  import useSWRMutations from "swr/mutation";
3
3
  import FileSaver from "file-saver";
4
- import { apiJson, apiJsonPost, JSON_OPTS } from "@truedat/core/services/api";
4
+ import {
5
+ apiJson,
6
+ apiJsonPost,
7
+ JSON_OPTS,
8
+ UPLOAD_JSON_OPTS,
9
+ } from "@truedat/core/services/api";
5
10
 
6
11
  import {
7
12
  API_CONCEPT_SUGGESTIONS,
@@ -9,7 +14,7 @@ import {
9
14
  API_CONCEPT_LINKS_DOWNLOAD,
10
15
  API_BUSINESS_CONCEPT_VERSIONS_SEARCH,
11
16
  API_BUSINESS_CONCEPT_VERSIONS_ACTIONS,
12
- API_BUSINESS_CONCEPT_VERSIONS_UPLOAD,
17
+ API_BUSINESS_CONCEPTS_XLSX_UPLOAD,
13
18
  API_CONCEPT_LINKS_UPLOAD,
14
19
  } from "../api";
15
20
 
@@ -58,8 +63,8 @@ export const useConceptLinksDownload = () => {
58
63
  };
59
64
 
60
65
  export const useConceptsUpload = () => {
61
- return useSWRMutations(API_BUSINESS_CONCEPT_VERSIONS_UPLOAD, (url, { arg }) =>
62
- apiJsonPost(url, arg),
66
+ return useSWRMutations(API_BUSINESS_CONCEPTS_XLSX_UPLOAD, (url, { arg }) =>
67
+ apiJsonPost(url, arg, UPLOAD_JSON_OPTS),
63
68
  );
64
69
  };
65
70
 
@@ -6,7 +6,7 @@ import {
6
6
  saveFile,
7
7
  } from "../downloadConcepts";
8
8
  import { downloadConcepts } from "../../routines";
9
- import { API_BUSINESS_CONCEPT_VERSIONS_XLSX } from "../../api";
9
+ import { API_BUSINESS_CONCEPTS_XLSX_DOWNLOAD } from "../../api";
10
10
 
11
11
  describe("sagas: downloadConceptsRequestSaga", () => {
12
12
  it("should invoke downloadConceptsSaga on downloadConcepts.TRIGGER", () => {
@@ -52,7 +52,7 @@ describe("sagas: downloadConceptsSaga", () => {
52
52
  .next()
53
53
  .put(downloadConcepts.request(body))
54
54
  .next()
55
- .call(apiJsonPost, API_BUSINESS_CONCEPT_VERSIONS_XLSX, body, {
55
+ .call(apiJsonPost, API_BUSINESS_CONCEPTS_XLSX_DOWNLOAD, body, {
56
56
  ...JSON_OPTS,
57
57
  responseType: "blob",
58
58
  })
@@ -76,7 +76,7 @@ describe("sagas: downloadConceptsSaga", () => {
76
76
  .next()
77
77
  .put(downloadConcepts.request(body))
78
78
  .next()
79
- .call(apiJsonPost, API_BUSINESS_CONCEPT_VERSIONS_XLSX, body, {
79
+ .call(apiJsonPost, API_BUSINESS_CONCEPTS_XLSX_DOWNLOAD, body, {
80
80
  ...JSON_OPTS,
81
81
  responseType: "blob",
82
82
  })
@@ -4,7 +4,7 @@ import { call, put, takeLatest } from "redux-saga/effects";
4
4
  import { apiJsonPost, JSON_OPTS } from "@truedat/core/services/api";
5
5
  import { CONCEPT_VERSION } from "@truedat/core/routes";
6
6
  import { downloadConcepts } from "../routines";
7
- import { API_BUSINESS_CONCEPT_VERSIONS_XLSX } from "../api";
7
+ import { API_BUSINESS_CONCEPTS_XLSX_DOWNLOAD } from "../api";
8
8
 
9
9
  export function saveFile({ data, headers }) {
10
10
  const contentDisposition = headers["content-disposition"];
@@ -25,7 +25,7 @@ export function* downloadConceptsSaga({ payload }) {
25
25
  lang,
26
26
  ...searchParams,
27
27
  };
28
- const url = API_BUSINESS_CONCEPT_VERSIONS_XLSX;
28
+ const url = API_BUSINESS_CONCEPTS_XLSX_DOWNLOAD;
29
29
  yield put(downloadConcepts.request(body));
30
30
  const { data, headers } = yield call(apiJsonPost, url, body, {
31
31
  ...JSON_OPTS,
@@ -0,0 +1,63 @@
1
+ import {
2
+ getConceptsUploadAlertMessage,
3
+ getUploadAlertMessage,
4
+ } from "../messages";
5
+
6
+ describe("getUploadAlertMessage", () => {
7
+ it("returns a success alert for status 200", () => {
8
+ expect(getUploadAlertMessage({ status: 200, data: {} })).toMatchObject({
9
+ error: false,
10
+ header: "concepts.upload.success.accepted.header",
11
+ });
12
+ });
13
+
14
+ it("returns a success alert for status 202", () => {
15
+ expect(getUploadAlertMessage({ status: 202, data: {} })).toMatchObject({
16
+ error: false,
17
+ header: "concepts.upload.success.accepted.header",
18
+ });
19
+ });
20
+
21
+ it("returns a failure alert for other statuses", () => {
22
+ expect(getUploadAlertMessage({ status: 422, data: {} })).toMatchObject({
23
+ error: true,
24
+ });
25
+ });
26
+ });
27
+
28
+ describe("getConceptsUploadAlertMessage", () => {
29
+ it("links to the created job detail on success with job_id", () => {
30
+ expect(
31
+ getConceptsUploadAlertMessage({
32
+ status: 200,
33
+ data: { job_id: 123 },
34
+ }),
35
+ ).toMatchObject({
36
+ error: false,
37
+ anchor: {
38
+ reference: "/concepts/uploadJobs/123",
39
+ label: "uploadJob.concepts.header",
40
+ },
41
+ });
42
+ });
43
+
44
+ it("keeps the upload jobs list anchor when there is no job_id", () => {
45
+ expect(
46
+ getConceptsUploadAlertMessage({ status: 202, data: {} }),
47
+ ).toMatchObject({
48
+ error: false,
49
+ anchor: {
50
+ reference: "/concepts/uploadJobs",
51
+ label: "sidemenu.concepts_upload_events",
52
+ },
53
+ });
54
+ });
55
+
56
+ it("returns a failure alert untouched when the upload fails", () => {
57
+ expect(
58
+ getConceptsUploadAlertMessage({ status: 422, data: {} }),
59
+ ).toMatchObject({
60
+ error: true,
61
+ });
62
+ });
63
+ });
@@ -1,9 +1,5 @@
1
1
  export { defaultConceptColumns, getConceptColumns } from "./getConceptColumns";
2
2
  export { getConceptDomainPath } from "./getConceptDomainPath";
3
- export {
4
- getConceptUploadEventColumns,
5
- defaultConceptsUploadEventColumns,
6
- } from "./getConceptUploadEventColumns";
7
3
  export { getParsedEvents } from "./getParsedEvents";
8
4
  export { getTemplateFields } from "./getTemplateFields";
9
5
  export { getTemplateGroups } from "./getTemplateGroups";
@@ -1,8 +1,8 @@
1
1
  import _ from "lodash/fp";
2
- import { CONCEPTS_BULK_UPLOAD_EVENTS } from "@truedat/core/routes";
2
+ import { CONCEPTS_UPLOAD_JOBS, linkTo } from "@truedat/core/routes";
3
3
 
4
4
  export const getUploadAlertMessage = ({ status, data }) => {
5
- if (status === 202) {
5
+ if (status === 200 || status === 202) {
6
6
  return {
7
7
  error: false,
8
8
  header: "concepts.upload.success.accepted.header",
@@ -10,7 +10,7 @@ export const getUploadAlertMessage = ({ status, data }) => {
10
10
  color: "blue",
11
11
  text: "",
12
12
  anchor: {
13
- reference: CONCEPTS_BULK_UPLOAD_EVENTS,
13
+ reference: CONCEPTS_UPLOAD_JOBS,
14
14
  label: "sidemenu.concepts_upload_events",
15
15
  },
16
16
  };
@@ -44,3 +44,20 @@ export const getUploadAlertMessage = ({ status, data }) => {
44
44
  return null;
45
45
  }
46
46
  };
47
+
48
+ export const getConceptsUploadAlertMessage = ({ status, data }) => {
49
+ const alertMessage = getUploadAlertMessage({ status, data });
50
+ const jobId = _.prop("job_id")(data);
51
+
52
+ if (alertMessage && !alertMessage.error && jobId != null) {
53
+ return {
54
+ ...alertMessage,
55
+ anchor: {
56
+ reference: linkTo.CONCEPTS_UPLOAD_JOB({ id: jobId }),
57
+ label: "uploadJob.concepts.header",
58
+ },
59
+ };
60
+ }
61
+
62
+ return alertMessage;
63
+ };
@@ -1,31 +0,0 @@
1
- import { Header, Icon, Segment } from "semantic-ui-react";
2
- import { FormattedMessage, useIntl } from "react-intl";
3
- import ConceptsUploadEventsTable from "./ConceptsUploadEventsTable";
4
-
5
- export const ConceptsUploadEvents = () => {
6
- const { formatMessage } = useIntl();
7
- return (
8
- <Segment>
9
- <Header as="h2">
10
- <Icon
11
- circular
12
- name={formatMessage({
13
- id: "conceptsUpload.header.icon",
14
- defaultMessage: "cogs",
15
- })}
16
- />
17
- <Header.Content>
18
- <FormattedMessage id="concepts.upload.header" />
19
- <Header.Subheader>
20
- <FormattedMessage id="concepts.upload.subheader" />
21
- </Header.Subheader>
22
- </Header.Content>
23
- </Header>
24
- <Segment attached="bottom">
25
- <ConceptsUploadEventsTable />
26
- </Segment>
27
- </Segment>
28
- );
29
- };
30
-
31
- export default ConceptsUploadEvents;
@@ -1,183 +0,0 @@
1
- import { useState } from "react";
2
- import PropTypes from "prop-types";
3
- import { connect } from "react-redux";
4
- import _ from "lodash/fp";
5
- import { FormattedMessage } from "react-intl";
6
- import { Table } from "semantic-ui-react";
7
- import { useIntl } from "react-intl";
8
- import { columnDecorator } from "@truedat/core/services";
9
- import { useUploadEvents } from "../hooks/useUploadEvents";
10
- import { getConceptUploadEventColumns } from "../selectors/getConceptUploadEventColumns";
11
- import "../styles/ConceptsUploadEventColumns.less";
12
-
13
- const getColumnsWithData = (conceptsResults, columns) =>
14
- _.filter((column) =>
15
- _.any(_.flow(columnDecorator(column), _.negate(_.isEmpty)))(
16
- conceptsResults,
17
- ),
18
- )(columns);
19
-
20
- const messagesImp = (data) => {
21
- const errors = data?.errors ?? [];
22
- return _.map((error) => {
23
- if (!error || !error.body)
24
- return {
25
- id: "unknown_error",
26
- context: {},
27
- defaultMessage: "concepts.upload.failed.success.errors",
28
- };
29
-
30
- return {
31
- id: error.body.message ?? "unknown_error",
32
- context: { ...error.body.context } ?? {},
33
- defaultMessage: "concepts.upload.failed.success.errors",
34
- };
35
- }, errors);
36
- };
37
-
38
- const ConceptsUploadEventsTable = ({ getColumns }) => {
39
- const { formatMessage } = useIntl();
40
- const [eventExpandedIndex, setEventExpandedIndex] = useState(null);
41
- const { data, loading } = useUploadEvents();
42
-
43
- const events =
44
- data && !loading
45
- ? _.reduce(
46
- (acc, event) => {
47
- const eventKey = `${event.task_reference}_${event.status}`;
48
-
49
- const node = event.node ?? "";
50
- const hasErrors =
51
- event.response?.errors && !_.isEmpty(event.response.errors);
52
-
53
- const messageId = _.startsWith("lm@", node)
54
- ? hasErrors
55
- ? "concepts.upload.success.header_with_errors_without_update"
56
- : "concepts.upload.success.header"
57
- : hasErrors
58
- ? "concepts.upload.success.header_with_errors"
59
- : "concepts.upload.success.header";
60
-
61
- const translatedHeader = event.response
62
- ? formatMessage(
63
- { id: messageId },
64
- {
65
- count_created: event.response.created?.length ?? 0,
66
- count_updated: event.response.updated?.length ?? 0,
67
- count_errors: event.response.errors?.length ?? 0,
68
- },
69
- )
70
- : "";
71
-
72
- const translatedErrors = _.map((error) =>
73
- formatMessage(
74
- { id: error.id ?? "unknown_error" },
75
- error.context ?? {},
76
- ),
77
- )(messagesImp(event.response));
78
-
79
- // eslint-disable-next-line fp/no-mutation
80
- acc[eventKey] = {
81
- ...event,
82
- response: {
83
- ...event.response,
84
- translatedHeader,
85
- translatedErrors,
86
- },
87
- expanded: false,
88
- };
89
-
90
- return acc;
91
- },
92
- {},
93
- data.data,
94
- )
95
- : [];
96
-
97
- const columns = getColumnsWithData(events, getColumns());
98
-
99
- const toggleExpanded = (eventKey) =>
100
- eventExpandedIndex === eventKey
101
- ? setEventExpandedIndex(null)
102
- : setEventExpandedIndex(eventKey);
103
-
104
- return (
105
- <>
106
- {!_.isEmpty(events) && (
107
- <Table sortable selectable>
108
- <Table.Header>
109
- <Table.Row>
110
- {columns.map((column, i) => (
111
- <Table.HeaderCell
112
- key={i}
113
- width={column.width}
114
- content={formatMessage({
115
- id: `concepts.upload.props.${column.name}`,
116
- defaultMessage: column.name,
117
- })}
118
- />
119
- ))}
120
- </Table.Row>
121
- </Table.Header>
122
- <Table.Body>
123
- {_.map(
124
- (eventKey) => (
125
- <Table.Row
126
- className={
127
- eventKey === eventExpandedIndex ? "expanded" : "contracted"
128
- }
129
- key={eventKey}
130
- onClick={() => toggleExpanded(eventKey)}
131
- >
132
- {eventKey !== eventExpandedIndex ? (
133
- columns &&
134
- columns.map((column, key) => (
135
- <Table.Cell
136
- key={key}
137
- textAlign={column.textAlign}
138
- content={columnDecorator(column)({
139
- ...events[eventKey],
140
- formatMessage,
141
- })}
142
- />
143
- ))
144
- ) : (
145
- <Table.Cell colSpan={columns.length}>
146
- {events[eventKey].file_hash ? (
147
- <>
148
- <FormattedMessage
149
- id={`concepts.upload.props.file_hash`}
150
- />
151
- : {events[eventKey].file_hash}
152
- </>
153
- ) : null}
154
- {columns.map((column, key) => (
155
- <div key={key}>
156
- {columnDecorator(column)({
157
- ...events[eventKey],
158
- formatMessage,
159
- })}
160
- </div>
161
- ))}
162
- </Table.Cell>
163
- )}
164
- </Table.Row>
165
- ),
166
- Object.keys(events),
167
- )}
168
- </Table.Body>
169
- </Table>
170
- )}
171
- </>
172
- );
173
- };
174
-
175
- ConceptsUploadEventsTable.propTypes = {
176
- getColumns: PropTypes.func,
177
- };
178
-
179
- const mapStateToProps = (state) => ({
180
- getColumns: () => getConceptUploadEventColumns(state),
181
- });
182
-
183
- export default connect(mapStateToProps)(ConceptsUploadEventsTable);