@truedat/df 5.17.3 → 5.18.1

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 (22) hide show
  1. package/package.json +4 -4
  2. package/src/templates/components/Template.js +64 -16
  3. package/src/templates/components/TemplateFilters.js +56 -0
  4. package/src/templates/components/TemplateRoutes.js +17 -11
  5. package/src/templates/components/Templates.js +45 -21
  6. package/src/templates/components/TemplatesContext.js +66 -0
  7. package/src/templates/components/TemplatesTable.js +113 -0
  8. package/src/templates/components/__tests__/TemplatesTable.spec.js +29 -0
  9. package/src/templates/components/__tests__/__snapshots__/Template.spec.js.snap +41 -16
  10. package/src/templates/components/__tests__/__snapshots__/TemplatesTable.spec.js.snap +3 -0
  11. package/src/templates/components/templateForm/FieldForm.js +2 -0
  12. package/src/templates/components/templateForm/TemplateForm.js +3 -8
  13. package/src/templates/components/templateForm/TemplateFormActions.js +21 -38
  14. package/src/templates/components/templateForm/__tests__/TemplateForm.spec.js +1 -1
  15. package/src/templates/components/templateForm/__tests__/TemplateFormActions.spec.js +0 -2
  16. package/src/templates/components/templateForm/__tests__/__snapshots__/TemplateForm.spec.js.snap +12 -12
  17. package/src/templates/components/templateForm/__tests__/__snapshots__/TemplateFormActions.spec.js.snap +34 -109
  18. package/src/templates/components/TemplateCards.js +0 -57
  19. package/src/templates/components/TemplateTabs.js +0 -40
  20. package/src/templates/components/__tests__/TemplateCards.spec.js +0 -34
  21. package/src/templates/components/__tests__/__snapshots__/TemplateCards.spec.js.snap +0 -62
  22. package/src/templates/components/scopes.js +0 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/df",
3
- "version": "5.17.3",
3
+ "version": "5.18.1",
4
4
  "description": "Truedat Web Data Quality Module",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -87,8 +87,8 @@
87
87
  },
88
88
  "dependencies": {
89
89
  "@apollo/client": "^3.7.1",
90
- "@truedat/auth": "5.17.3",
91
- "@truedat/core": "5.17.3",
90
+ "@truedat/auth": "5.18.1",
91
+ "@truedat/core": "5.18.1",
92
92
  "decode-uri-component": "^0.2.2",
93
93
  "path-to-regexp": "^1.7.0",
94
94
  "prop-types": "^15.8.1",
@@ -109,5 +109,5 @@
109
109
  "react-dom": ">= 16.8.6 < 17",
110
110
  "semantic-ui-react": ">= 2.0.3 < 2.2"
111
111
  },
112
- "gitHead": "ec07aeeabffbb6e46d2d22ade283de203dd94c88"
112
+ "gitHead": "95d5a42f5bd259ad53fa66d4fe8cce48be73ee03"
113
113
  }
@@ -1,30 +1,72 @@
1
1
  import _ from "lodash/fp";
2
2
  import React from "react";
3
3
  import PropTypes from "prop-types";
4
- import { Header, Icon, Segment } from "semantic-ui-react";
4
+ import { FormattedMessage } from "react-intl";
5
+ import {
6
+ Button,
7
+ Header,
8
+ Icon,
9
+ Segment,
10
+ Grid,
11
+ Container,
12
+ } from "semantic-ui-react";
5
13
  import { connect } from "react-redux";
14
+ import { ConfirmModal } from "@truedat/core/components";
6
15
  import { deleteTemplate, updateTemplate } from "../routines";
7
16
  import TemplateCrumbs from "./TemplateCrumbs";
8
17
  import TemplateForm from "./templateForm/TemplateForm";
9
18
 
10
- export const Template = ({ template, updateTemplate, deleteTemplate }) =>
19
+ export const Template = ({
20
+ template,
21
+ updateTemplate,
22
+ deleteTemplate,
23
+ templateDeleting,
24
+ templateSaving,
25
+ }) =>
11
26
  _.isUndefined(template.id) ? null : (
12
27
  <>
13
28
  <TemplateCrumbs name={template?.label} />
14
29
  <Segment>
15
- <Header as="h2">
16
- <Icon name="file code outline" circular />
17
- <Header.Content>
18
- {template?.label}
19
- <Header.Subheader>{template?.name}</Header.Subheader>
20
- </Header.Content>
21
- </Header>
22
- <TemplateForm
23
- template={template}
24
- onDelete={deleteTemplate}
25
- onSubmit={updateTemplate}
26
- editMode
27
- />
30
+ <Grid>
31
+ <Grid.Column width={8}>
32
+ <Header as="h2">
33
+ <Icon name="file code outline" circular />
34
+ <Header.Content>
35
+ {template?.label}
36
+ <Header.Subheader>{template?.name}</Header.Subheader>
37
+ </Header.Content>
38
+ </Header>
39
+ </Grid.Column>
40
+ <Grid.Column width={8}>
41
+ <Container textAlign="right">
42
+ {template.id && (
43
+ <ConfirmModal
44
+ icon="trash"
45
+ trigger={
46
+ <Button
47
+ negative
48
+ icon="trash"
49
+ loading={templateDeleting}
50
+ disabled={templateSaving || templateDeleting}
51
+ />
52
+ }
53
+ header={
54
+ <FormattedMessage id="template.actions.delete.confirmation.header" />
55
+ }
56
+ content={
57
+ <FormattedMessage
58
+ id="template.actions.delete.confirmation.content"
59
+ values={{ name: <i>{template.name}</i> }}
60
+ />
61
+ }
62
+ onConfirm={() => deleteTemplate(template)}
63
+ />
64
+ )}
65
+ </Container>
66
+ </Grid.Column>
67
+ </Grid>
68
+
69
+ <TemplateForm template={template} onSubmit={updateTemplate} editMode />
28
70
  </Segment>
29
71
  </>
30
72
  );
@@ -33,9 +75,15 @@ Template.propTypes = {
33
75
  deleteTemplate: PropTypes.func,
34
76
  template: PropTypes.object,
35
77
  updateTemplate: PropTypes.func,
78
+ templateDeleting: PropTypes.bool,
79
+ templateSaving: PropTypes.bool,
36
80
  };
37
81
 
38
- const mapStateToProps = ({ template }) => ({ template });
82
+ const mapStateToProps = ({ template, templateDeleting, templateSaving }) => ({
83
+ template,
84
+ templateDeleting,
85
+ templateSaving,
86
+ });
39
87
 
40
88
  export default connect(mapStateToProps, { updateTemplate, deleteTemplate })(
41
89
  Template
@@ -0,0 +1,56 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import { Input, Dropdown } from "semantic-ui-react";
4
+ import { useIntl, FormattedMessage } from "react-intl";
5
+ import { lowerDeburrTrim } from "@truedat/core/services/sort";
6
+ import { useTemplatesContext } from "./TemplatesContext";
7
+
8
+ export default function TemplateFilters() {
9
+ const { formatMessage } = useIntl();
10
+ const { query, setQuery, scope, scopes, setScope } = useTemplatesContext();
11
+ return (
12
+ <Input
13
+ value={query}
14
+ onChange={(_e, data) => setQuery(lowerDeburrTrim(data.value))}
15
+ icon={{ name: "search", link: true }}
16
+ iconPosition="left"
17
+ action={
18
+ <Dropdown
19
+ button
20
+ className="icon"
21
+ floating
22
+ icon="filter"
23
+ labeled
24
+ scrolling
25
+ text={formatMessage({
26
+ id: scope ? `template.scope.${scope}` : "templates.all_scopes",
27
+ })}
28
+ upward={false}
29
+ >
30
+ <Dropdown.Menu>
31
+ <Dropdown.Item onClick={() => setScope()}>
32
+ <em>
33
+ <FormattedMessage id="templates.all_scopes" />
34
+ </em>
35
+ </Dropdown.Item>
36
+ {_.flow(
37
+ _.defaultTo([]),
38
+ _.map((scope) => (
39
+ <Dropdown.Item
40
+ key={scope}
41
+ text={formatMessage({
42
+ id: `template.scope.${scope}`,
43
+ })}
44
+ onClick={() => setScope(scope)}
45
+ />
46
+ ))
47
+ )(scopes)}
48
+ </Dropdown.Menu>
49
+ </Dropdown>
50
+ }
51
+ placeholder={formatMessage({
52
+ id: "templates.search.placeholder",
53
+ })}
54
+ />
55
+ );
56
+ }
@@ -2,17 +2,13 @@ import React from "react";
2
2
  import { Route, Switch } from "react-router-dom";
3
3
  import { Unauthorized } from "@truedat/core/components";
4
4
  import { useAuthorized } from "@truedat/core/hooks";
5
- import {
6
- TEMPLATE,
7
- TEMPLATES,
8
- TEMPLATES_NEW,
9
- TEMPLATE_SCOPE,
10
- } from "@truedat/core/routes";
5
+ import { TEMPLATE, TEMPLATES, TEMPLATES_NEW } from "@truedat/core/routes";
11
6
  import NewTemplate from "./NewTemplate";
12
7
  import Templates from "./Templates";
13
8
  import Template from "./Template";
14
9
  import TemplatesLoader from "./TemplatesLoader";
15
10
  import TemplateLoader from "./TemplateLoader";
11
+ import TemplatesContextProvider from "./TemplatesContext";
16
12
 
17
13
  export const TemplateRoutes = () => {
18
14
  const authorized = useAuthorized();
@@ -21,11 +17,21 @@ export const TemplateRoutes = () => {
21
17
  path={TEMPLATES}
22
18
  render={() =>
23
19
  authorized ? (
24
- <>
25
- <Route path={TEMPLATES} component={TemplatesLoader} exact />
26
- <Route path={TEMPLATES} component={Templates} exact />
27
- <Route path={TEMPLATE_SCOPE} component={Templates} exact />
20
+ <TemplatesContextProvider
21
+ initialSortColumn="updated_at"
22
+ initialSortDirection="descending"
23
+ >
28
24
  <Switch>
25
+ <Route
26
+ path={TEMPLATES}
27
+ render={() => (
28
+ <>
29
+ <TemplatesLoader />
30
+ <Templates />
31
+ </>
32
+ )}
33
+ exact
34
+ />
29
35
  <Route
30
36
  path={TEMPLATES_NEW}
31
37
  render={() => <NewTemplate />}
@@ -41,7 +47,7 @@ export const TemplateRoutes = () => {
41
47
  )}
42
48
  />
43
49
  </Switch>
44
- </>
50
+ </TemplatesContextProvider>
45
51
  ) : (
46
52
  <Unauthorized />
47
53
  )
@@ -1,25 +1,49 @@
1
1
  import React from "react";
2
- import { Header, Icon, Segment } from "semantic-ui-react";
3
2
  import { FormattedMessage } from "react-intl";
4
- import TemplateTabs from "./TemplateTabs";
5
- import TemplateCards from "./TemplateCards";
3
+ import {
4
+ Header,
5
+ Icon,
6
+ Segment,
7
+ Grid,
8
+ Container,
9
+ Button,
10
+ } from "semantic-ui-react";
11
+ import { Link } from "react-router-dom";
12
+ import { TEMPLATES_NEW } from "@truedat/core/routes";
13
+ import TemplateFilters from "./TemplateFilters";
14
+ import TemplatesTable from "./TemplatesTable";
6
15
 
7
- const Templates = () => (
8
- <Segment>
9
- <Header as="h2">
10
- <Icon name="file code outline" circular />
11
- <Header.Content>
12
- <FormattedMessage id="templates.header" />
13
- <Header.Subheader>
14
- <FormattedMessage id="templates.subheader" />
15
- </Header.Subheader>
16
- </Header.Content>
17
- </Header>
18
- <TemplateTabs />
19
- <Segment attached="bottom">
20
- <TemplateCards />
21
- </Segment>
22
- </Segment>
23
- );
16
+ export default function Templates() {
17
+ return (
18
+ <Segment>
19
+ <Header as="h2">
20
+ <Icon name="file code outline" circular />
21
+ <Header.Content>
22
+ <FormattedMessage id="templates.header" />
23
+ <Header.Subheader>
24
+ <FormattedMessage id="templates.subheader" />
25
+ </Header.Subheader>
26
+ </Header.Content>
27
+ </Header>
28
+
29
+ <Grid>
30
+ <Grid.Column width={8}>
31
+ <TemplateFilters />
32
+ </Grid.Column>
24
33
 
25
- export default Templates;
34
+ <Grid.Column width={8}>
35
+ <Container textAlign="right">
36
+ <Button
37
+ primary
38
+ as={Link}
39
+ to={TEMPLATES_NEW}
40
+ content={<FormattedMessage id="templates.actions.create" />}
41
+ />
42
+ </Container>
43
+ </Grid.Column>
44
+ </Grid>
45
+
46
+ <TemplatesTable />
47
+ </Segment>
48
+ );
49
+ }
@@ -0,0 +1,66 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState, useContext, createContext } from "react";
3
+ import { useSelector } from "react-redux";
4
+ import { matchSorter } from "match-sorter";
5
+
6
+ export const TemplatesContext = createContext();
7
+ export const useTemplatesContext = () => useContext(TemplatesContext);
8
+
9
+ const HIDDEN_SCOPES = ["cx", "ca"];
10
+ const isHiddenScope = (scope) => _.includes(scope)(HIDDEN_SCOPES);
11
+
12
+ export default function TemplatesContextProvider(props) {
13
+ const children = _.prop("children")(props);
14
+ const initialSortColumn = _.prop("initialSortColumn")(props);
15
+ const initialSortDirection = _.prop("initialSortDirection")(props);
16
+
17
+ const { templates, templatesLoading: loading } = useSelector(
18
+ _.pick(["templates", "templatesLoading"])
19
+ );
20
+
21
+ const [query, setQuery] = useState("");
22
+ const [scope, setScope] = useState();
23
+
24
+ const [sortColumn, setSortColumn] = useState(initialSortColumn);
25
+ const [sortDirection, setSortDirection] = useState(initialSortDirection);
26
+
27
+ const parsedTemplates = _.flow(
28
+ _.reject(({ scope }) => isHiddenScope(scope)),
29
+ _.filter((template) => !scope || scope == template.scope),
30
+ (items) =>
31
+ matchSorter(items, query, {
32
+ keys: ["name", "label"],
33
+ threshold: matchSorter.rankings.CONTAINS,
34
+ }),
35
+ _.orderBy([sortColumn], [sortDirection == "ascending" ? "asc" : "desc"])
36
+ )(templates);
37
+
38
+ const scopes = _.flow(
39
+ _.map("scope"),
40
+ _.uniq,
41
+ _.reject(isHiddenScope)
42
+ )(templates);
43
+
44
+ const context = {
45
+ loading,
46
+ templates: parsedTemplates,
47
+
48
+ query,
49
+ setQuery,
50
+
51
+ scope,
52
+ setScope,
53
+ scopes,
54
+
55
+ sortColumn,
56
+ sortDirection,
57
+ setSortColumn,
58
+ setSortDirection,
59
+ };
60
+
61
+ return (
62
+ <TemplatesContext.Provider value={context}>
63
+ {children}
64
+ </TemplatesContext.Provider>
65
+ );
66
+ }
@@ -0,0 +1,113 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import { FormattedMessage } from "react-intl";
4
+ import { Link } from "react-router-dom";
5
+ import { Table, Header, Icon } from "semantic-ui-react";
6
+ import { linkTo } from "@truedat/core/routes";
7
+ import { sortColumn as sortHandler } from "@truedat/core/services/sort";
8
+ import { columnDecorator } from "@truedat/core/services";
9
+ import {
10
+ TranslateDecorator,
11
+ DateDecorator,
12
+ } from "@truedat/core/services/columnDecorators";
13
+
14
+ import { useTemplatesContext } from "./TemplatesContext";
15
+
16
+ export default function TemplatesTable() {
17
+ const {
18
+ templates,
19
+ sortColumn,
20
+ sortDirection,
21
+ setSortColumn,
22
+ setSortDirection,
23
+ } = useTemplatesContext();
24
+
25
+ const columns = [
26
+ {
27
+ name: "name",
28
+ sort: { name: "name" },
29
+ fieldSelector: _.pick(["id", "name"]),
30
+ fieldDecorator: ({ id, name }) => (
31
+ <Link to={linkTo.TEMPLATE({ templateId: id })}>{name}</Link>
32
+ ),
33
+ },
34
+ {
35
+ name: "label",
36
+ sort: { name: "label" },
37
+ },
38
+ {
39
+ name: "scope",
40
+ sort: { name: "scope" },
41
+ fieldSelector: ({ scope }) => ({ id: `template.scope.${scope}` }),
42
+ fieldDecorator: TranslateDecorator,
43
+ width: 2,
44
+ },
45
+ {
46
+ name: "updated_at",
47
+ sort: { name: "updated_at" },
48
+ fieldSelector: ({ updated_at }) => ({
49
+ date: updated_at,
50
+ }),
51
+ width: 2,
52
+ fieldDecorator: DateDecorator,
53
+ textAlign: "center",
54
+ },
55
+ ];
56
+
57
+ return _.isEmpty(templates) ? (
58
+ <Header as="h4">
59
+ <Icon name="search" />
60
+ <Header.Content>
61
+ <FormattedMessage id="templates.search.results.empty" />
62
+ </Header.Content>
63
+ </Header>
64
+ ) : (
65
+ <Table sortable>
66
+ <Table.Header>
67
+ <Table.Row>
68
+ {columns.map((column, key) => (
69
+ <Table.HeaderCell
70
+ key={key}
71
+ width={column.width}
72
+ content={
73
+ <FormattedMessage
74
+ id={`template.form.${column.header || column.name}`}
75
+ defaultMessage={column.name}
76
+ />
77
+ }
78
+ sorted={
79
+ _.path("sort.name")(column) === sortColumn
80
+ ? sortDirection
81
+ : null
82
+ }
83
+ className={_.path("sort.name")(column) ? "" : "disabled"}
84
+ onClick={() =>
85
+ sortHandler(
86
+ column,
87
+ () => {},
88
+ setSortDirection,
89
+ setSortColumn,
90
+ sortDirection,
91
+ sortColumn
92
+ )
93
+ }
94
+ />
95
+ ))}
96
+ </Table.Row>
97
+ </Table.Header>
98
+ <Table.Body>
99
+ {templates.map((t, i) => (
100
+ <Table.Row key={i}>
101
+ {columns.map((column, i) => (
102
+ <Table.Cell
103
+ key={i}
104
+ textAlign={column.textAlign}
105
+ content={columnDecorator(column)(t)}
106
+ />
107
+ ))}
108
+ </Table.Row>
109
+ ))}
110
+ </Table.Body>
111
+ </Table>
112
+ );
113
+ }
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ import { shallowWithIntl } from "@truedat/test/intl-stub";
3
+ import TemplatesTable from "../TemplatesTable";
4
+ import { TemplatesContext } from "../TemplatesContext";
5
+
6
+ describe("<TemplatesTable />", () => {
7
+ const templates = [
8
+ {
9
+ scope: "bg",
10
+ name: "testbg",
11
+ label: "testbg",
12
+ },
13
+ {
14
+ scope: "dq",
15
+ name: "testdq",
16
+ label: "testdq",
17
+ },
18
+ ];
19
+ const context = { templates };
20
+
21
+ it("matches the latest snapshot", () => {
22
+ const wrapper = shallowWithIntl(
23
+ <TemplatesContext.Provider values={context}>
24
+ <TemplatesTable />
25
+ </TemplatesContext.Provider>
26
+ );
27
+ expect(wrapper).toMatchSnapshot();
28
+ });
29
+ });
@@ -24,24 +24,48 @@ exports[`<Template /> matches the latest snapshot 1`] = `
24
24
  <div
25
25
  class="ui segment"
26
26
  >
27
- <h2
28
- class="ui header"
27
+ <div
28
+ class="ui grid"
29
29
  >
30
- <i
31
- aria-hidden="true"
32
- class="file code outline circular icon"
33
- />
34
30
  <div
35
- class="content"
31
+ class="eight wide column"
32
+ >
33
+ <h2
34
+ class="ui header"
35
+ >
36
+ <i
37
+ aria-hidden="true"
38
+ class="file code outline circular icon"
39
+ />
40
+ <div
41
+ class="content"
42
+ >
43
+ My Template
44
+ <div
45
+ class="sub header"
46
+ >
47
+ template1
48
+ </div>
49
+ </div>
50
+ </h2>
51
+ </div>
52
+ <div
53
+ class="eight wide column"
36
54
  >
37
- My Template
38
55
  <div
39
- class="sub header"
56
+ class="ui right aligned container"
40
57
  >
41
- template1
58
+ <button
59
+ class="ui icon negative button"
60
+ >
61
+ <i
62
+ aria-hidden="true"
63
+ class="trash icon"
64
+ />
65
+ </button>
42
66
  </div>
43
67
  </div>
44
- </h2>
68
+ </div>
45
69
  <form
46
70
  class="ui form"
47
71
  >
@@ -68,6 +92,7 @@ exports[`<Template /> matches the latest snapshot 1`] = `
68
92
  >
69
93
  <input
70
94
  name="name"
95
+ readonly=""
71
96
  required=""
72
97
  type="text"
73
98
  value="template1"
@@ -297,17 +322,17 @@ exports[`<Template /> matches the latest snapshot 1`] = `
297
322
  class="ui divider"
298
323
  />
299
324
  <div
300
- class="actions"
325
+ class="ui right aligned container"
301
326
  >
302
327
  <button
303
- class="ui negative button"
328
+ class="ui primary button"
304
329
  >
305
- Delete
330
+ Save
306
331
  </button>
307
332
  <button
308
- class="ui primary button"
333
+ class="ui button"
309
334
  >
310
- Save
335
+ Cancel
311
336
  </button>
312
337
  </div>
313
338
  </form>
@@ -0,0 +1,3 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<TemplatesTable /> matches the latest snapshot 1`] = `<TemplatesTable />`;
@@ -258,6 +258,8 @@ export const FieldForm = ({
258
258
  value={boost}
259
259
  required
260
260
  onChange={onChange}
261
+ type="number"
262
+ step="0.01"
261
263
  />
262
264
  </Form.Field>
263
265
  ) : null}
@@ -28,7 +28,7 @@ const scopeOptions = (formatMessage) =>
28
28
  "quality_control",
29
29
  ]);
30
30
 
31
- export const TemplateForm = ({ loading, template, onSubmit, onDelete }) => {
31
+ export const TemplateForm = ({ loading, template, onSubmit }) => {
32
32
  const { formatMessage } = useIntl();
33
33
  const [activeGroup, setActiveGroup] = useState(0);
34
34
  const [editedTemplate, setEditedTemplate] = useState({
@@ -94,7 +94,6 @@ export const TemplateForm = ({ loading, template, onSubmit, onDelete }) => {
94
94
  )(editedTemplate);
95
95
  onSubmit({ template: { ...editedTemplate, content: formattedContent } });
96
96
  };
97
- const handleDelete = () => onDelete({ id: template.id });
98
97
 
99
98
  const validatedContent = parseContentValidation(editedTemplate.content);
100
99
 
@@ -113,6 +112,7 @@ export const TemplateForm = ({ loading, template, onSubmit, onDelete }) => {
113
112
  value={editedTemplate.name || ""}
114
113
  required
115
114
  onChange={handleChange}
115
+ readOnly={!_.isEmpty(template)}
116
116
  />
117
117
  <Form.Input
118
118
  name="label"
@@ -173,11 +173,7 @@ export const TemplateForm = ({ loading, template, onSubmit, onDelete }) => {
173
173
  </Grid.Column>
174
174
  </Grid>
175
175
  <Divider />
176
- <TemplateFormActions
177
- template={template}
178
- onSave={handleSubmit}
179
- onDelete={handleDelete}
180
- />
176
+ <TemplateFormActions template={template} onSave={handleSubmit} />
181
177
  </Form>
182
178
  );
183
179
  };
@@ -186,7 +182,6 @@ TemplateForm.propTypes = {
186
182
  loading: PropTypes.bool.isRequired,
187
183
  template: PropTypes.object.isRequired,
188
184
  onSubmit: PropTypes.func.isRequired,
189
- onDelete: PropTypes.func,
190
185
  };
191
186
 
192
187
  const mapStateToProps = ({ templateDeleting, templateSaving }) => ({
@@ -1,56 +1,39 @@
1
1
  import React from "react";
2
2
  import PropTypes from "prop-types";
3
3
  import { connect } from "react-redux";
4
- import { Button } from "semantic-ui-react";
4
+ import { useHistory } from "react-router-dom";
5
+ import { Button, Container } from "semantic-ui-react";
6
+ import { TEMPLATES } from "@truedat/core/routes";
5
7
  import { FormattedMessage } from "react-intl";
6
- import { ConfirmModal } from "@truedat/core/components";
7
8
 
8
9
  export const TemplateFormActions = ({
9
- template: { name, id },
10
10
  templateSaving,
11
11
  templateDeleting,
12
- onDelete,
13
12
  onSave,
14
- }) => (
15
- <div className="actions">
16
- {id && (
17
- <ConfirmModal
18
- icon="trash"
19
- trigger={
20
- <Button
21
- negative
22
- content={<FormattedMessage id="actions.delete" />}
23
- loading={templateDeleting}
24
- disabled={templateSaving || templateDeleting}
25
- />
26
- }
27
- header={
28
- <FormattedMessage id="template.actions.delete.confirmation.header" />
29
- }
30
- content={
31
- <FormattedMessage
32
- id="template.actions.delete.confirmation.content"
33
- values={{ name: <i>{name}</i> }}
34
- />
35
- }
36
- onConfirm={onDelete}
13
+ }) => {
14
+ const history = useHistory();
15
+ const onCancel = () => history.push(TEMPLATES);
16
+ return (
17
+ <Container textAlign="right">
18
+ <Button
19
+ primary
20
+ content={<FormattedMessage id="actions.save" />}
21
+ onClick={onSave}
22
+ loading={templateSaving}
23
+ disabled={templateSaving || templateDeleting}
37
24
  />
38
- )}
39
- <Button
40
- primary
41
- content={<FormattedMessage id="actions.save" />}
42
- onClick={onSave}
43
- loading={templateSaving}
44
- disabled={templateSaving || templateDeleting}
45
- />
46
- </div>
47
- );
25
+ <Button
26
+ content={<FormattedMessage id="actions.cancel" />}
27
+ onClick={onCancel}
28
+ />
29
+ </Container>
30
+ );
31
+ };
48
32
 
49
33
  TemplateFormActions.propTypes = {
50
34
  template: PropTypes.object.isRequired,
51
35
  templateDeleting: PropTypes.bool.isRequired,
52
36
  templateSaving: PropTypes.bool.isRequired,
53
- onDelete: PropTypes.func.isRequired,
54
37
  onSave: PropTypes.func.isRequired,
55
38
  };
56
39
 
@@ -10,7 +10,7 @@ const template = {
10
10
  };
11
11
  const messages = {
12
12
  en: {
13
- "actions.delete": "actions.delete",
13
+ "actions.cancel": "actions.cancel",
14
14
  "actions.save": "actions.save",
15
15
  "template.form.fieldGroups": "template.form.fieldGroups",
16
16
  "template.form.header": "template.form.header",
@@ -4,7 +4,6 @@ import { TemplateFormActions } from "../TemplateFormActions";
4
4
 
5
5
  describe("<TemplateFormActions />", () => {
6
6
  const onSave = jest.fn();
7
- const onDelete = jest.fn();
8
7
  const template = {
9
8
  id: 1,
10
9
  name: "Name",
@@ -15,7 +14,6 @@ describe("<TemplateFormActions />", () => {
15
14
  template,
16
15
  templateSaving,
17
16
  templateDeleting,
18
- onDelete,
19
17
  onSave,
20
18
  };
21
19
 
@@ -28,6 +28,7 @@ exports[`<TemplateForm /> matches the latest snapshot (loading) 1`] = `
28
28
  >
29
29
  <input
30
30
  name="name"
31
+ readonly=""
31
32
  required=""
32
33
  type="text"
33
34
  value="Name"
@@ -255,21 +256,19 @@ exports[`<TemplateForm /> matches the latest snapshot (loading) 1`] = `
255
256
  class="ui divider"
256
257
  />
257
258
  <div
258
- class="actions"
259
+ class="ui right aligned container"
259
260
  >
260
261
  <button
261
- class="ui negative disabled button"
262
+ class="ui loading primary disabled button"
262
263
  disabled=""
263
264
  tabindex="-1"
264
265
  >
265
- actions.delete
266
+ actions.save
266
267
  </button>
267
268
  <button
268
- class="ui loading primary disabled button"
269
- disabled=""
270
- tabindex="-1"
269
+ class="ui button"
271
270
  >
272
- actions.save
271
+ actions.cancel
273
272
  </button>
274
273
  </div>
275
274
  </form>
@@ -304,6 +303,7 @@ exports[`<TemplateForm /> matches the latest snapshot 1`] = `
304
303
  >
305
304
  <input
306
305
  name="name"
306
+ readonly=""
307
307
  required=""
308
308
  type="text"
309
309
  value="Name"
@@ -531,17 +531,17 @@ exports[`<TemplateForm /> matches the latest snapshot 1`] = `
531
531
  class="ui divider"
532
532
  />
533
533
  <div
534
- class="actions"
534
+ class="ui right aligned container"
535
535
  >
536
536
  <button
537
- class="ui negative button"
537
+ class="ui primary button"
538
538
  >
539
- actions.delete
539
+ actions.save
540
540
  </button>
541
541
  <button
542
- class="ui primary button"
542
+ class="ui button"
543
543
  >
544
- actions.save
544
+ actions.cancel
545
545
  </button>
546
546
  </div>
547
547
  </form>
@@ -1,43 +1,9 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
3
  exports[`<TemplateFormActions /> matches the latest snapshot (deleting) 1`] = `
4
- <div
5
- className="actions"
4
+ <Container
5
+ textAlign="right"
6
6
  >
7
- <ConfirmModal
8
- content={
9
- <Memo(MemoizedFormattedMessage)
10
- id="template.actions.delete.confirmation.content"
11
- values={
12
- {
13
- "name": <i>
14
- Name
15
- </i>,
16
- }
17
- }
18
- />
19
- }
20
- header={
21
- <Memo(MemoizedFormattedMessage)
22
- id="template.actions.delete.confirmation.header"
23
- />
24
- }
25
- icon="trash"
26
- onConfirm={[MockFunction]}
27
- trigger={
28
- <Button
29
- as="button"
30
- content={
31
- <Memo(MemoizedFormattedMessage)
32
- id="actions.delete"
33
- />
34
- }
35
- disabled={true}
36
- loading={true}
37
- negative={true}
38
- />
39
- }
40
- />
41
7
  <Button
42
8
  as="button"
43
9
  content={
@@ -50,47 +16,22 @@ exports[`<TemplateFormActions /> matches the latest snapshot (deleting) 1`] = `
50
16
  onClick={[MockFunction]}
51
17
  primary={true}
52
18
  />
53
- </div>
54
- `;
55
-
56
- exports[`<TemplateFormActions /> matches the latest snapshot (saving) 1`] = `
57
- <div
58
- className="actions"
59
- >
60
- <ConfirmModal
19
+ <Button
20
+ as="button"
61
21
  content={
62
22
  <Memo(MemoizedFormattedMessage)
63
- id="template.actions.delete.confirmation.content"
64
- values={
65
- {
66
- "name": <i>
67
- Name
68
- </i>,
69
- }
70
- }
71
- />
72
- }
73
- header={
74
- <Memo(MemoizedFormattedMessage)
75
- id="template.actions.delete.confirmation.header"
76
- />
77
- }
78
- icon="trash"
79
- onConfirm={[MockFunction]}
80
- trigger={
81
- <Button
82
- as="button"
83
- content={
84
- <Memo(MemoizedFormattedMessage)
85
- id="actions.delete"
86
- />
87
- }
88
- disabled={true}
89
- loading={false}
90
- negative={true}
23
+ id="actions.cancel"
91
24
  />
92
25
  }
26
+ onClick={[Function]}
93
27
  />
28
+ </Container>
29
+ `;
30
+
31
+ exports[`<TemplateFormActions /> matches the latest snapshot (saving) 1`] = `
32
+ <Container
33
+ textAlign="right"
34
+ >
94
35
  <Button
95
36
  as="button"
96
37
  content={
@@ -103,47 +44,22 @@ exports[`<TemplateFormActions /> matches the latest snapshot (saving) 1`] = `
103
44
  onClick={[MockFunction]}
104
45
  primary={true}
105
46
  />
106
- </div>
107
- `;
108
-
109
- exports[`<TemplateFormActions /> matches the latest snapshot 1`] = `
110
- <div
111
- className="actions"
112
- >
113
- <ConfirmModal
47
+ <Button
48
+ as="button"
114
49
  content={
115
50
  <Memo(MemoizedFormattedMessage)
116
- id="template.actions.delete.confirmation.content"
117
- values={
118
- {
119
- "name": <i>
120
- Name
121
- </i>,
122
- }
123
- }
124
- />
125
- }
126
- header={
127
- <Memo(MemoizedFormattedMessage)
128
- id="template.actions.delete.confirmation.header"
129
- />
130
- }
131
- icon="trash"
132
- onConfirm={[MockFunction]}
133
- trigger={
134
- <Button
135
- as="button"
136
- content={
137
- <Memo(MemoizedFormattedMessage)
138
- id="actions.delete"
139
- />
140
- }
141
- disabled={false}
142
- loading={false}
143
- negative={true}
51
+ id="actions.cancel"
144
52
  />
145
53
  }
54
+ onClick={[Function]}
146
55
  />
56
+ </Container>
57
+ `;
58
+
59
+ exports[`<TemplateFormActions /> matches the latest snapshot 1`] = `
60
+ <Container
61
+ textAlign="right"
62
+ >
147
63
  <Button
148
64
  as="button"
149
65
  content={
@@ -156,5 +72,14 @@ exports[`<TemplateFormActions /> matches the latest snapshot 1`] = `
156
72
  onClick={[MockFunction]}
157
73
  primary={true}
158
74
  />
159
- </div>
75
+ <Button
76
+ as="button"
77
+ content={
78
+ <Memo(MemoizedFormattedMessage)
79
+ id="actions.cancel"
80
+ />
81
+ }
82
+ onClick={[Function]}
83
+ />
84
+ </Container>
160
85
  `;
@@ -1,57 +0,0 @@
1
- import _ from "lodash/fp";
2
- import React from "react";
3
- import PropTypes from "prop-types";
4
- import queryString from "query-string";
5
- import { FormattedMessage } from "react-intl";
6
- import { connect } from "react-redux";
7
- import { Link, useLocation } from "react-router-dom";
8
- import { Icon, Card } from "semantic-ui-react";
9
- import { TEMPLATES_NEW } from "@truedat/core/routes";
10
- import TemplateCard from "./TemplateCard";
11
- import { filterScopeWithTemplates, validScope } from "./scopes";
12
-
13
- export const TemplateCards = ({ templates }) => {
14
- const { search } = useLocation();
15
- const { scope } = queryString.parse(search);
16
- const defaultScope = _.flow(
17
- _.map(_.prop("scope")),
18
- _.filter(validScope),
19
- _.head
20
- )(templates);
21
-
22
- const selectedScope =
23
- filterScopeWithTemplates(scope, templates) || defaultScope;
24
- const visibleTemplates = _.filter(_.propEq("scope", selectedScope))(
25
- templates
26
- );
27
-
28
- return (
29
- <Card.Group>
30
- <Card link as={Link} to={TEMPLATES_NEW} className="create template">
31
- <Card.Content textAlign="center">
32
- <Card.Header>
33
- <FormattedMessage id="templates.actions.create" />
34
- </Card.Header>
35
- <Card.Description>
36
- <Icon.Group floated="left" size="big">
37
- <Icon name="file outline" />
38
- <Icon corner name="add" />
39
- </Icon.Group>
40
- </Card.Description>
41
- </Card.Content>
42
- </Card>
43
-
44
- {visibleTemplates.map((template, i) => (
45
- <TemplateCard key={i} template={template} />
46
- ))}
47
- </Card.Group>
48
- );
49
- };
50
-
51
- TemplateCards.propTypes = {
52
- templates: PropTypes.array,
53
- };
54
-
55
- const mapStateToProps = ({ templates }) => ({ templates });
56
-
57
- export default connect(mapStateToProps)(TemplateCards);
@@ -1,40 +0,0 @@
1
- import _ from "lodash/fp";
2
- import React from "react";
3
- import PropTypes from "prop-types";
4
- import { connect } from "react-redux";
5
- import { Link, useLocation } from "react-router-dom";
6
- import { Menu } from "semantic-ui-react";
7
- import { FormattedMessage } from "react-intl";
8
- import queryString from "query-string";
9
- import { linkTo } from "@truedat/core/routes";
10
- import { filterScope } from "./scopes";
11
-
12
- const TemplateTabs = ({ scopes }) => {
13
- const { search } = useLocation();
14
- const { scope: selectedScope } = queryString.parse(search);
15
- const activeTab = _.max([0, _.findIndex(s => s == selectedScope)(scopes)]);
16
- return (
17
- <Menu attached="top" secondary pointing tabular>
18
- {scopes.map((scope, i) => (
19
- <Menu.Item
20
- key={i}
21
- active={i === activeTab}
22
- as={Link}
23
- to={linkTo.TEMPLATE_SCOPE({ scope })}
24
- >
25
- <FormattedMessage id={`templates.tabs.${scope}`} />
26
- </Menu.Item>
27
- ))}
28
- </Menu>
29
- );
30
- };
31
-
32
- TemplateTabs.propTypes = {
33
- scopes: PropTypes.array
34
- };
35
-
36
- const mapStateToProps = ({ templates }) => ({
37
- scopes: _.flow(_.map("scope"), _.uniq, _.filter(filterScope))(templates)
38
- });
39
-
40
- export default connect(mapStateToProps)(TemplateTabs);
@@ -1,34 +0,0 @@
1
- import React from "react";
2
- import { shallowWithIntl } from "@truedat/test/intl-stub";
3
- import { TemplateCards } from "../TemplateCards";
4
-
5
- jest.mock("react-router-dom", () => ({
6
- ...jest.requireActual("react-router-dom"),
7
- useLocation: () => ({
8
- hash: "",
9
- key: "v5q3jz",
10
- pathname: "/templates",
11
- search: "?scope=bg"
12
- })
13
- }));
14
-
15
- describe("<TemplateCards />", () => {
16
- const templates = [
17
- {
18
- scope: "bg",
19
- name: "testbg",
20
- label: "testbg"
21
- },
22
- {
23
- scope: "dq",
24
- name: "testdq",
25
- label: "testdq"
26
- }
27
- ];
28
- const props = { templates };
29
-
30
- it("matches the latest snapshot", () => {
31
- const wrapper = shallowWithIntl(<TemplateCards {...props} />);
32
- expect(wrapper).toMatchSnapshot();
33
- });
34
- });
@@ -1,62 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`<TemplateCards /> matches the latest snapshot 1`] = `
4
- <CardGroup>
5
- <Card
6
- as={
7
- {
8
- "$$typeof": Symbol(react.forward_ref),
9
- "displayName": "Link",
10
- "propTypes": {
11
- "innerRef": [Function],
12
- "onClick": [Function],
13
- "replace": [Function],
14
- "target": [Function],
15
- "to": [Function],
16
- },
17
- "render": [Function],
18
- }
19
- }
20
- className="create template"
21
- link={true}
22
- to="/templates/new"
23
- >
24
- <CardContent
25
- textAlign="center"
26
- >
27
- <CardHeader>
28
- <MemoizedFormattedMessage
29
- id="templates.actions.create"
30
- />
31
- </CardHeader>
32
- <CardDescription>
33
- <IconGroup
34
- as="i"
35
- floated="left"
36
- size="big"
37
- >
38
- <Icon
39
- as="i"
40
- name="file outline"
41
- />
42
- <Icon
43
- as="i"
44
- corner={true}
45
- name="add"
46
- />
47
- </IconGroup>
48
- </CardDescription>
49
- </CardContent>
50
- </Card>
51
- <TemplateCard
52
- key="0"
53
- template={
54
- {
55
- "label": "testbg",
56
- "name": "testbg",
57
- "scope": "bg",
58
- }
59
- }
60
- />
61
- </CardGroup>
62
- `;
@@ -1,8 +0,0 @@
1
- import _ from "lodash/fp";
2
-
3
- export const validScope = (scope) => _.negate(_.includes(scope))(["cx", "ca"]);
4
- export const filterScope = (scope) => (validScope(scope) ? scope : null);
5
- export const filterScopeWithTemplates = (scope, templates) =>
6
- validScope(scope) && _.flow(_.map("scope"), _.includes(scope))(templates)
7
- ? scope
8
- : null;