@truedat/dd 4.48.12 → 4.49.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 (89) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +5 -5
  3. package/src/api/fragments.js +2 -2
  4. package/src/api/mutations.js +33 -11
  5. package/src/api/queries.js +37 -10
  6. package/src/api.js +0 -4
  7. package/src/components/DictionaryRoutes.js +4 -4
  8. package/src/components/StructureSummary.js +12 -22
  9. package/src/components/StructureTag.js +83 -54
  10. package/src/components/StructureTagForm.js +149 -89
  11. package/src/components/StructureTags.js +57 -27
  12. package/src/components/Tag.js +62 -0
  13. package/src/components/TagCards.js +85 -0
  14. package/src/components/TagCrumbs.js +25 -0
  15. package/src/components/TagForm.js +102 -0
  16. package/src/components/TagStructureButton.js +58 -0
  17. package/src/components/Tags.js +33 -0
  18. package/src/components/TagsRoutes.js +39 -0
  19. package/src/components/__tests__/StructureSummary.spec.js +0 -7
  20. package/src/components/__tests__/StructureTag.spec.js +79 -5
  21. package/src/components/__tests__/StructureTagForm.spec.js +99 -50
  22. package/src/components/__tests__/Tag.spec.js +11 -0
  23. package/src/components/__tests__/{StructureTagsCards.spec.js → TagCards.spec.js} +10 -10
  24. package/src/components/__tests__/TagCrumbs.spec.js +10 -0
  25. package/src/components/__tests__/TagForm.spec.js +72 -0
  26. package/src/components/__tests__/Tags.spec.js +10 -0
  27. package/src/components/__tests__/__snapshots__/StructureSummary.spec.js.snap +18 -42
  28. package/src/components/__tests__/__snapshots__/StructureTag.spec.js.snap +10 -30
  29. package/src/components/__tests__/__snapshots__/StructureTagForm.spec.js.snap +255 -51
  30. package/src/components/__tests__/__snapshots__/Tag.spec.js.snap +36 -0
  31. package/src/components/__tests__/__snapshots__/{StructureTagsCards.spec.js.snap → TagCards.spec.js.snap} +1 -1
  32. package/src/components/__tests__/__snapshots__/TagCrumbs.spec.js.snap +102 -0
  33. package/src/components/__tests__/__snapshots__/TagForm.spec.js.snap +70 -0
  34. package/src/components/__tests__/__snapshots__/{StructureTags.spec.js.snap → Tags.spec.js.snap} +1 -1
  35. package/src/messages/en.js +25 -30
  36. package/src/messages/es.js +25 -30
  37. package/src/reducers/__tests__/{editableStructureTags.spec.js → availableTags.spec.js} +7 -11
  38. package/src/reducers/__tests__/structureTagSaving.spec.js +36 -0
  39. package/src/reducers/__tests__/tagSaving.spec.js +32 -0
  40. package/src/reducers/{editableStructureTags.js → availableTags.js} +2 -2
  41. package/src/reducers/index.js +6 -12
  42. package/src/reducers/structureRedirect.js +7 -7
  43. package/src/reducers/structureTagSaving.js +12 -10
  44. package/src/reducers/tagSaving.js +18 -0
  45. package/src/routines.js +5 -6
  46. package/src/sagas/__tests__/createTag.spec.js +81 -0
  47. package/src/sagas/__tests__/deleteStructureTag.spec.js +11 -12
  48. package/src/sagas/__tests__/deleteTag.spec.js +76 -0
  49. package/src/sagas/__tests__/tagStructure.spec.js +86 -0
  50. package/src/sagas/__tests__/updateTag.spec.js +81 -0
  51. package/src/sagas/createTag.js +29 -0
  52. package/src/sagas/deleteStructureTag.js +12 -9
  53. package/src/sagas/deleteTag.js +34 -0
  54. package/src/sagas/index.js +15 -18
  55. package/src/sagas/tagStructure.js +37 -0
  56. package/src/sagas/updateTag.js +23 -0
  57. package/src/components/LinkStructureTagForm.js +0 -163
  58. package/src/components/LinkedStructureTag.js +0 -124
  59. package/src/components/StructureLinkTagButton.js +0 -47
  60. package/src/components/StructureTagsCards.js +0 -92
  61. package/src/components/StructureTagsCrumbs.js +0 -44
  62. package/src/components/StructureTagsRoutes.js +0 -45
  63. package/src/components/TagsHeader.js +0 -32
  64. package/src/components/__tests__/LinkStructureTagForm.spec.js +0 -184
  65. package/src/components/__tests__/LinkedStructureTag.spec.js +0 -133
  66. package/src/components/__tests__/StructureTags.spec.js +0 -14
  67. package/src/components/__tests__/StructureTagsCrumbs.spec.js +0 -13
  68. package/src/components/__tests__/__snapshots__/LinkStructureTagForm.spec.js.snap +0 -456
  69. package/src/components/__tests__/__snapshots__/LinkedStructureTag.spec.js.snap +0 -16
  70. package/src/components/__tests__/__snapshots__/StructureTagsCrumbs.spec.js.snap +0 -36
  71. package/src/reducers/__tests__/deletingStructureTags.spec.js +0 -30
  72. package/src/reducers/__tests__/linkedStructureTags.spec.js +0 -69
  73. package/src/reducers/__tests__/linkedStructureTagsLoading.spec.js +0 -32
  74. package/src/reducers/__tests__/linkingStructureTags.spec.js +0 -30
  75. package/src/reducers/__tests__/structureTagsSaving.spec.js +0 -40
  76. package/src/reducers/deletingStructureTags.js +0 -16
  77. package/src/reducers/linkedStructureTags.js +0 -28
  78. package/src/reducers/linkedStructureTagsLoading.js +0 -16
  79. package/src/reducers/linkingStructureTags.js +0 -16
  80. package/src/sagas/__tests__/createStructureTag.spec.js +0 -85
  81. package/src/sagas/__tests__/deleteLinkStructureTag.spec.js +0 -96
  82. package/src/sagas/__tests__/fetchLinksStructureTags.spec.js +0 -105
  83. package/src/sagas/__tests__/linkStructureTag.spec.js +0 -102
  84. package/src/sagas/__tests__/updateStructureTag.spec.js +0 -84
  85. package/src/sagas/createStructureTag.js +0 -29
  86. package/src/sagas/deleteLinkStructureTag.js +0 -29
  87. package/src/sagas/fetchLinksStructureTags.js +0 -31
  88. package/src/sagas/linkStructureTag.js +0 -30
  89. package/src/sagas/updateStructureTag.js +0 -23
@@ -1,110 +1,170 @@
1
1
  import _ from "lodash/fp";
2
- import React, { useState } from "react";
3
- import PropTypes from "prop-types";
4
- import { Button, Form } from "semantic-ui-react";
2
+ import React, { useEffect } from "react";
5
3
  import { useForm, Controller } from "react-hook-form";
4
+ import { Button, Container, Form } from "semantic-ui-react";
5
+ import { connect } from "react-redux";
6
+ import PropTypes from "prop-types";
6
7
  import { useIntl } from "react-intl";
7
- import { HistoryBackButton, DomainSelector } from "@truedat/core/components";
8
+ import { accentInsensitivePathOrder } from "@truedat/core/services/sort";
9
+ import { deleteStructureTag, tagStructure } from "../routines";
10
+
11
+ const DEFAULT_VALUES = {
12
+ dataStructureId: null,
13
+ tagId: null,
14
+ id: null,
15
+ inherit: false,
16
+ comment: "",
17
+ };
8
18
 
9
19
  export const StructureTagForm = ({
10
- loading,
11
- onSubmit,
20
+ deleteStructureTag,
12
21
  saving,
13
22
  structureTag,
23
+ tagStructure,
24
+ tags,
14
25
  }) => {
15
- const [domainsLoading, setDomainsLoading] = useState(true);
16
26
  const { formatMessage } = useIntl();
17
- const { handleSubmit, control, formState } = useForm({
27
+
28
+ const options = _.flow(
29
+ _.prop("tag"),
30
+ _.defaultTo(tags),
31
+ _.castArray,
32
+ _.sortBy(accentInsensitivePathOrder("name")),
33
+ _.map(({ id, name, description }) => ({
34
+ value: id,
35
+ text: name,
36
+ description: description,
37
+ }))
38
+ )(structureTag);
39
+ const { handleSubmit, control, formState, setValue } = useForm({
18
40
  mode: "all",
19
- defaultValues: _.pick(["name", "description", "domainIds"])(structureTag),
41
+ defaultValues: {
42
+ ...DEFAULT_VALUES,
43
+ ..._.pick(["dataStructureId", "id", "inherit", "comment", "tagId"])(
44
+ structureTag
45
+ ),
46
+ },
20
47
  });
21
-
48
+ useEffect(() => {
49
+ if (!structureTag.tagId && _.size(options) === 1) {
50
+ const tagId = _.head(options).value;
51
+ setValue("tagId", tagId, {
52
+ shouldDirty: true,
53
+ shouldValidate: true,
54
+ });
55
+ }
56
+ }, [options, setValue, structureTag]);
22
57
  const { isDirty, isValid } = formState;
23
-
24
58
  return (
25
- <Form onSubmit={handleSubmit(onSubmit)} loading={loading || domainsLoading}>
26
- <Controller
27
- control={control}
28
- name="name"
29
- rules={{ required: true }}
30
- render={({
31
- field: { onBlur, onChange, value },
32
- fieldState: { error },
33
- }) => (
34
- <Form.Input
35
- autoComplete="off"
36
- error={!!error}
37
- id="name"
38
- label={formatMessage({ id: "structureTagForm.props.name" })}
39
- onBlur={onBlur}
40
- onChange={(_e, { value }) => onChange(value)}
41
- placeholder={formatMessage({
42
- id: "structureTagForm.props.name",
43
- })}
44
- value={value}
45
- required
46
- />
47
- )}
48
- />
49
- <Controller
50
- control={control}
51
- name="description"
52
- rules={{ required: false }}
53
- render={({
54
- field: { onBlur, onChange, value },
55
- fieldState: { error },
56
- }) => (
57
- <Form.Input
58
- autoComplete="off"
59
- error={error}
60
- id="description"
61
- label={formatMessage({ id: "structureTagForm.props.description" })}
62
- onBlur={onBlur}
63
- onChange={(_e, { value }) => onChange(value)}
64
- placeholder={formatMessage({
65
- id: "structureTagForm.props.description",
66
- })}
67
- value={value}
68
- />
69
- )}
70
- />
71
- <Controller
72
- control={control}
73
- name="domainIds"
74
- render={({ field: { onChange, value } }) => (
75
- <DomainSelector
76
- action="manageTags"
77
- value={value}
78
- onLoad={() => setDomainsLoading(false)}
79
- onChange={(_e, { value }) => onChange(value)}
80
- label={formatMessage({ id: "structureTagForm.props.domains" })}
81
- />
82
- )}
83
- />
84
- <div className="actions">
85
- <Button
86
- floated="right"
87
- type="submit"
88
- primary
89
- loading={saving}
90
- disabled={saving || !isDirty || !isValid}
91
- >
92
- {formatMessage({ id: "actions.save" })}
93
- </Button>
94
- <HistoryBackButton
95
- content={formatMessage({ id: "actions.cancel" })}
96
- disabled={saving}
59
+ <Container className="link-structure-tag-form">
60
+ <Form
61
+ onSubmit={handleSubmit((data) => tagStructure(data))}
62
+ loading={saving}
63
+ >
64
+ <Controller
65
+ control={control}
66
+ name="tagId"
67
+ rules={{ required: true }}
68
+ render={({
69
+ field: { onBlur, onChange, value },
70
+ fieldState: { error },
71
+ }) => (
72
+ <Form.Dropdown
73
+ name="tagId"
74
+ disabled={!_.isNil(structureTag.tagId)}
75
+ error={!!error}
76
+ placeholder={formatMessage({
77
+ id: "structureTag.tag.placeholder",
78
+ })}
79
+ label={formatMessage({ id: "structureTag.tag" })}
80
+ onBlur={onBlur}
81
+ onChange={(_e, { value }) => onChange(value)}
82
+ clearable={_.size(options) > 1}
83
+ search
84
+ selection
85
+ options={options}
86
+ value={value}
87
+ />
88
+ )}
89
+ />
90
+ <Controller
91
+ control={control}
92
+ name="comment"
93
+ render={({
94
+ field: { onBlur, onChange, value },
95
+ fieldState: { error },
96
+ }) => (
97
+ <Form.TextArea
98
+ id="comment"
99
+ error={!!error}
100
+ name="comment"
101
+ label={{
102
+ children: formatMessage({ id: "structureTag.comment" }),
103
+ htmlFor: "comment",
104
+ }}
105
+ placeholder={formatMessage({
106
+ id: "structureTag.comment.placeholder",
107
+ })}
108
+ onBlur={onBlur}
109
+ onChange={(_e, { value }) => onChange(value)}
110
+ value={value}
111
+ />
112
+ )}
113
+ />
114
+ <Controller
115
+ control={control}
116
+ name="inherit"
117
+ render={({
118
+ field: { onBlur, onChange, value },
119
+ fieldState: { error },
120
+ }) => (
121
+ <Form.Checkbox
122
+ id="inherit"
123
+ error={!!error}
124
+ name="inherit"
125
+ label={{
126
+ htmlFor: "inherit",
127
+ children: formatMessage({ id: "structureTag.inherit" }),
128
+ }}
129
+ onBlur={onBlur}
130
+ onChange={(_e, { checked }) => onChange(checked)}
131
+ checked={value}
132
+ />
133
+ )}
97
134
  />
98
- </div>
99
- </Form>
135
+ <div className="actions">
136
+ <Button
137
+ type="submit"
138
+ disabled={!!saving || !isDirty || !isValid}
139
+ primary
140
+ content={formatMessage({ id: "actions.save" })}
141
+ />
142
+ {structureTag.inherited === false ? (
143
+ <Button
144
+ type="button"
145
+ onClick={() => deleteStructureTag(structureTag.id)}
146
+ disabled={!!saving}
147
+ secondary
148
+ content={formatMessage({ id: "actions.delete" })}
149
+ />
150
+ ) : null}
151
+ </div>
152
+ </Form>
153
+ </Container>
100
154
  );
101
155
  };
102
156
 
103
157
  StructureTagForm.propTypes = {
104
- onSubmit: PropTypes.func.isRequired,
105
- loading: PropTypes.bool,
158
+ deleteStructureTag: PropTypes.func,
106
159
  saving: PropTypes.bool,
107
- structureTag: PropTypes.object,
160
+ structureTag: PropTypes.object.isRequired,
161
+ tagStructure: PropTypes.func,
162
+ tags: PropTypes.array,
163
+ };
164
+
165
+ const mapDispatchToProps = {
166
+ deleteStructureTag,
167
+ tagStructure,
108
168
  };
109
169
 
110
- export default StructureTagForm;
170
+ export default connect(null, mapDispatchToProps)(StructureTagForm);
@@ -1,33 +1,63 @@
1
+ import _ from "lodash/fp";
1
2
  import React from "react";
2
- import { Link } from "react-router-dom";
3
- import { Button, Header, Icon, Segment } from "semantic-ui-react";
4
- import { useIntl, FormattedMessage } from "react-intl";
5
- import { STRUCTURE_TAGS_NEW } from "@truedat/core/routes";
6
- import StructureTagsCards from "./StructureTagsCards";
3
+ import PropTypes from "prop-types";
4
+ import { useParams } from "react-router-dom";
5
+ import { useQuery } from "@apollo/client";
6
+ import { Loading } from "@truedat/core/components";
7
+ import { STRUCTURE_TAGS_QUERY } from "../api/queries";
8
+ import StructureTag from "./StructureTag";
9
+ import TagStructureButton from "./TagStructureButton";
7
10
 
8
- export const StructureTags = () => {
9
- const { formatMessage } = useIntl();
11
+ export const StructureTags = ({ structureId, structureTags, tags }) => {
12
+ const editableIds = _.map("id")(tags);
13
+ const taggedIds = _.flow(
14
+ _.reject("inherited"),
15
+ _.map("tag.id")
16
+ )(structureTags);
17
+ const availableTags = _.reject(({ id }) => _.includes(id)(taggedIds))(tags);
10
18
  return (
11
- <Segment>
12
- <Header as="h2">
13
- <Button
14
- floated="right"
15
- primary
16
- as={Link}
17
- to={STRUCTURE_TAGS_NEW}
18
- content={formatMessage({ id: "structureTags.actions.create" })}
19
- />
20
- <Icon name="tags" circular />
21
- <Header.Content>
22
- <FormattedMessage id="structureTags.header" />
23
- <Header.Subheader>
24
- <FormattedMessage id="structureTags.subheader" />
25
- </Header.Subheader>
26
- </Header.Content>
27
- </Header>
28
- <StructureTagsCards />
29
- </Segment>
19
+ <>
20
+ {_.isEmpty(structureTags)
21
+ ? null
22
+ : structureTags.map((structureTag, i) => (
23
+ <StructureTag
24
+ key={i}
25
+ editable={
26
+ structureTag?.inherited
27
+ ? false
28
+ : _.includes(structureTag?.tag?.id)(editableIds)
29
+ }
30
+ structureTag={structureTag}
31
+ />
32
+ ))}
33
+ {_.isEmpty(availableTags) ? null : (
34
+ <TagStructureButton structureId={structureId} tags={availableTags} />
35
+ )}
36
+ </>
30
37
  );
31
38
  };
32
39
 
33
- export default StructureTags;
40
+ StructureTags.propTypes = {
41
+ structureId: PropTypes.string,
42
+ structureTags: PropTypes.array,
43
+ tags: PropTypes.array,
44
+ };
45
+
46
+ export const StructureTagsLoader = () => {
47
+ const { id } = useParams();
48
+ const { loading, error, data } = useQuery(STRUCTURE_TAGS_QUERY, {
49
+ fetchPolicy: "cache-and-network",
50
+ variables: { id },
51
+ });
52
+ if (error) return null;
53
+ if (loading) return <Loading />;
54
+ return (
55
+ <StructureTags
56
+ structureId={id}
57
+ structureTags={_.sortBy("insertedAt")(data?.dataStructure?.structureTags)}
58
+ tags={_.sortBy("name")(data?.dataStructure?.availableTags)}
59
+ />
60
+ );
61
+ };
62
+
63
+ export default StructureTagsLoader;
@@ -0,0 +1,62 @@
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+ import { Header, Icon, Container, Segment } from "semantic-ui-react";
4
+ import { FormattedMessage } from "react-intl";
5
+ import { connect } from "react-redux";
6
+ import { Loading } from "@truedat/core/components";
7
+ import { createTag, updateTag } from "../routines";
8
+ import TagCrumbs from "./TagCrumbs";
9
+ import TagForm from "./TagForm";
10
+
11
+ const DEFAULT_VALUES = {
12
+ name: "",
13
+ domainIds: [],
14
+ };
15
+
16
+ export const Tag = ({
17
+ tag = DEFAULT_VALUES,
18
+ updateTag,
19
+ createTag,
20
+ loading,
21
+ saving,
22
+ id,
23
+ }) => {
24
+ const action = id ? "tags.actions.edit" : "tags.actions.create";
25
+ const onSubmit = id ? (data) => updateTag({ ...data, id }) : createTag;
26
+ return (
27
+ <>
28
+ <TagCrumbs action={action} />
29
+ <Container text as={Segment}>
30
+ <Header as="h2">
31
+ <Icon name="tag" />
32
+ <Header.Content>
33
+ <FormattedMessage id={action} />
34
+ </Header.Content>
35
+ </Header>
36
+ {loading ? (
37
+ <Loading />
38
+ ) : (
39
+ <TagForm tag={tag} onSubmit={onSubmit} saving={saving} />
40
+ )}
41
+ </Container>
42
+ </>
43
+ );
44
+ };
45
+
46
+ Tag.propTypes = {
47
+ tag: PropTypes.object,
48
+ updateTag: PropTypes.func,
49
+ createTag: PropTypes.func,
50
+ loading: PropTypes.bool,
51
+ saving: PropTypes.bool,
52
+ id: PropTypes.string,
53
+ };
54
+
55
+ const mapStateToProps = ({ tagSaving }) => ({
56
+ saving: tagSaving,
57
+ });
58
+
59
+ export default connect(mapStateToProps, {
60
+ createTag,
61
+ updateTag,
62
+ })(Tag);
@@ -0,0 +1,85 @@
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 } from "react-router-dom";
6
+ import { Button, Card, Message, Icon } from "semantic-ui-react";
7
+ import { FormattedMessage } from "react-intl";
8
+ import { useQuery } from "@apollo/client";
9
+ import { ConfirmModal, Loading } from "@truedat/core/components";
10
+ import { linkTo } from "@truedat/core/routes";
11
+ import { accentInsensitivePathOrder } from "@truedat/core/services/sort";
12
+ import { TAGS_QUERY } from "../api/queries";
13
+ import { deleteTag } from "../routines";
14
+
15
+ const EmptyMessage = () => (
16
+ <Message icon info>
17
+ <Icon name="folder open outline" />
18
+ <Message.Content>
19
+ <Message.Header>
20
+ <FormattedMessage id="tags.empty" />
21
+ </Message.Header>
22
+ </Message.Content>
23
+ </Message>
24
+ );
25
+
26
+ export const TagCards = ({ deleteTag, tags }) =>
27
+ _.isEmpty(tags) ? (
28
+ <EmptyMessage />
29
+ ) : (
30
+ <Card.Group>
31
+ {tags.map(({ id, name, description, structureCount }) => (
32
+ <Card key={id}>
33
+ <Card.Content>
34
+ <Card.Header as={Link} to={linkTo.STRUCTURE_TAG_EDIT({ id })}>
35
+ {name}
36
+ </Card.Header>
37
+ <Card.Description>{description}</Card.Description>
38
+ </Card.Content>
39
+ <Card.Content extra>
40
+ <div className="ui actions">
41
+ <ConfirmModal
42
+ icon="trash"
43
+ trigger={<Button icon="trash" basic color="red" />}
44
+ header={
45
+ <FormattedMessage id="tags.actions.delete.confirmation.header" />
46
+ }
47
+ content={
48
+ structureCount > 0 ? (
49
+ <FormattedMessage
50
+ id="tags.actions.delete.confirmation.content"
51
+ values={{
52
+ name: <i>{`'${name}'`}</i>,
53
+ count: structureCount,
54
+ }}
55
+ />
56
+ ) : (
57
+ <FormattedMessage
58
+ id="tags.actions.delete.noStructures.confirmation.content"
59
+ values={{ name: <i>{`'${name}'`}</i> }}
60
+ />
61
+ )
62
+ }
63
+ onConfirm={() => deleteTag({ id })}
64
+ />
65
+ </div>
66
+ </Card.Content>
67
+ </Card>
68
+ ))}
69
+ </Card.Group>
70
+ );
71
+
72
+ TagCards.propTypes = {
73
+ tags: PropTypes.array,
74
+ deleteTag: PropTypes.func,
75
+ };
76
+
77
+ export const TagCardsLoader = (props) => {
78
+ const { loading, error, data } = useQuery(TAGS_QUERY);
79
+ if (error) return null;
80
+ if (loading) return <Loading />;
81
+ const tags = _.sortBy(accentInsensitivePathOrder("name"))(data?.tags || []);
82
+ return <TagCards tags={tags} {...props} />;
83
+ };
84
+
85
+ export default connect(null, { deleteTag })(TagCardsLoader);
@@ -0,0 +1,25 @@
1
+ import _ from "lodash/fp";
2
+ import React from "react";
3
+ import PropTypes from "prop-types";
4
+ import { Breadcrumb } from "semantic-ui-react";
5
+ import { Link } from "react-router-dom";
6
+ import { FormattedMessage } from "react-intl";
7
+ import { STRUCTURE_TAGS } from "@truedat/core/routes";
8
+
9
+ export const TagCrumbs = ({ action }) => (
10
+ <Breadcrumb>
11
+ <Breadcrumb.Section as={Link} to={STRUCTURE_TAGS} active={false}>
12
+ <FormattedMessage id="tags.crumbs.root" />
13
+ </Breadcrumb.Section>
14
+ <Breadcrumb.Divider icon="right angle" />
15
+ <Breadcrumb.Section active>
16
+ <FormattedMessage id={action} defaultMessage={action} />
17
+ </Breadcrumb.Section>
18
+ </Breadcrumb>
19
+ );
20
+
21
+ TagCrumbs.propTypes = {
22
+ action: PropTypes.string.isRequired,
23
+ };
24
+
25
+ export default TagCrumbs;
@@ -0,0 +1,102 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useState } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { Button, Form } from "semantic-ui-react";
5
+ import { useForm, Controller } from "react-hook-form";
6
+ import { useIntl } from "react-intl";
7
+ import { HistoryBackButton, DomainSelector } from "@truedat/core/components";
8
+
9
+ export const TagForm = ({ loading, onSubmit, saving, tag }) => {
10
+ const [domainsLoading, setDomainsLoading] = useState(true);
11
+ const { formatMessage } = useIntl();
12
+ const { handleSubmit, control, formState } = useForm({
13
+ mode: "all",
14
+ defaultValues: _.pick(["name", "description", "domainIds"])(tag),
15
+ });
16
+
17
+ const { isDirty, isValid } = formState;
18
+
19
+ return (
20
+ <Form onSubmit={handleSubmit(onSubmit)} loading={loading || domainsLoading}>
21
+ <Controller
22
+ control={control}
23
+ name="name"
24
+ rules={{ required: true }}
25
+ render={({
26
+ field: { onBlur, onChange, value },
27
+ fieldState: { error },
28
+ }) => (
29
+ <Form.Input
30
+ autoComplete="off"
31
+ error={!!error}
32
+ id="name"
33
+ label={formatMessage({ id: "tagForm.props.name" })}
34
+ onBlur={onBlur}
35
+ onChange={(_e, { value }) => onChange(value)}
36
+ placeholder={formatMessage({ id: "tagForm.props.name" })}
37
+ value={value}
38
+ required
39
+ />
40
+ )}
41
+ />
42
+ <Controller
43
+ control={control}
44
+ name="description"
45
+ rules={{ required: false }}
46
+ render={({
47
+ field: { onBlur, onChange, value },
48
+ fieldState: { error },
49
+ }) => (
50
+ <Form.Input
51
+ autoComplete="off"
52
+ error={error}
53
+ id="description"
54
+ label={formatMessage({ id: "tagForm.props.description" })}
55
+ onBlur={onBlur}
56
+ onChange={(_e, { value }) => onChange(value)}
57
+ placeholder={formatMessage({ id: "tagForm.props.description" })}
58
+ value={value}
59
+ />
60
+ )}
61
+ />
62
+ <Controller
63
+ control={control}
64
+ name="domainIds"
65
+ render={({ field: { onChange, value } }) => (
66
+ <DomainSelector
67
+ action="manageTags"
68
+ label={formatMessage({ id: "tagForm.props.domains" })}
69
+ multiple
70
+ onChange={(_e, { value }) => onChange(value)}
71
+ onLoad={() => setDomainsLoading(false)}
72
+ value={value}
73
+ />
74
+ )}
75
+ />
76
+ <div className="actions">
77
+ <Button
78
+ floated="right"
79
+ type="submit"
80
+ primary
81
+ loading={saving}
82
+ disabled={saving || !isDirty || !isValid}
83
+ >
84
+ {formatMessage({ id: "actions.save" })}
85
+ </Button>
86
+ <HistoryBackButton
87
+ content={formatMessage({ id: "actions.cancel" })}
88
+ disabled={saving}
89
+ />
90
+ </div>
91
+ </Form>
92
+ );
93
+ };
94
+
95
+ TagForm.propTypes = {
96
+ onSubmit: PropTypes.func.isRequired,
97
+ loading: PropTypes.bool,
98
+ saving: PropTypes.bool,
99
+ tag: PropTypes.object,
100
+ };
101
+
102
+ export default TagForm;