@truedat/df 7.2.11 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/df",
3
- "version": "7.2.11",
3
+ "version": "7.3.0",
4
4
  "description": "Truedat Web Data Quality Module",
5
5
  "sideEffects": false,
6
6
  "jsnext:main": "src/index.js",
@@ -34,7 +34,7 @@
34
34
  "@testing-library/jest-dom": "^5.16.5",
35
35
  "@testing-library/react": "^12.0.0",
36
36
  "@testing-library/user-event": "^13.2.1",
37
- "@truedat/test": "7.2.11",
37
+ "@truedat/test": "7.3.0",
38
38
  "babel-jest": "^28.1.0",
39
39
  "babel-plugin-dynamic-import-node": "^2.3.3",
40
40
  "babel-plugin-lodash": "^3.3.4",
@@ -87,8 +87,8 @@
87
87
  },
88
88
  "dependencies": {
89
89
  "@apollo/client": "^3.7.1",
90
- "@truedat/auth": "7.2.11",
91
- "@truedat/core": "7.2.11",
90
+ "@truedat/auth": "7.3.0",
91
+ "@truedat/core": "7.3.0",
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": "74eb04f81dd49275229cf3050b56b897ff78410e"
112
+ "gitHead": "cbf85fe17b535953689418b85658cc804d095dfb"
113
113
  }
@@ -0,0 +1,115 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useEffect } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { useIntl } from "react-intl";
5
+ import { connect } from "react-redux";
6
+ import { useLanguage } from "@truedat/core/i18n";
7
+ import { makeGetApplyTemplate, makeGetTemplate } from "../selectors";
8
+ import { parseGroupsWithLangs } from "../utils";
9
+ import FieldGroupWithTranslations from "./FieldGroupWithTranslations";
10
+
11
+ export const DynamicFormWithTranslations = ({
12
+ applyTemplate,
13
+ i18nContent,
14
+ fieldsToOmit,
15
+ isModification,
16
+ loading,
17
+ onChange,
18
+ selectedDomain,
19
+ template,
20
+ }) => {
21
+ const { formatMessage } = useIntl();
22
+ const { defaultLang, requiredLangs } = useLanguage();
23
+ const domainId = selectedDomain?.id;
24
+ useEffect(() => {
25
+ if (!_.isNil(domainId) && !_.isEmpty(template)) {
26
+ const defaultContent = i18nContent[defaultLang]?.content;
27
+ if (defaultContent) {
28
+ const newContent = applyTemplate(defaultContent, domainId);
29
+ onChange(defaultLang, { content: newContent });
30
+ }
31
+ }
32
+ }, [applyTemplate, domainId, template, defaultLang, i18nContent, onChange]);
33
+
34
+ if (
35
+ loading ||
36
+ _.isEmpty(template) ||
37
+ _.isNil(template) ||
38
+ !_.isArray(template.content)
39
+ ) {
40
+ return null;
41
+ }
42
+
43
+ const handleChange = (e, { lang, name, value }) => {
44
+ e && e.preventDefault();
45
+
46
+ const currentContent = i18nContent[lang] || {};
47
+ const newLanguageContent = {
48
+ ...currentContent,
49
+ [name]: {
50
+ value: value === "" ? null : value,
51
+ origin: "user",
52
+ },
53
+ };
54
+ const newContent = applyTemplate(newLanguageContent, domainId);
55
+ onChange(lang, { content: newContent });
56
+ };
57
+
58
+ const parsedGroups = parseGroupsWithLangs(formatMessage)(
59
+ template,
60
+ i18nContent,
61
+ fieldsToOmit,
62
+ selectedDomain,
63
+ requiredLangs
64
+ );
65
+
66
+ return parsedGroups.map(({ name, langs }, i) => (
67
+ <FieldGroupWithTranslations
68
+ key={i}
69
+ name={
70
+ name
71
+ ? formatMessage({
72
+ id: `templates.groups.${name}`,
73
+ defaultMessage: name,
74
+ })
75
+ : null
76
+ }
77
+ scope={template?.scope}
78
+ fieldsByLang={langs}
79
+ onFieldChange={handleChange}
80
+ isModification={isModification}
81
+ />
82
+ ));
83
+ };
84
+
85
+ DynamicFormWithTranslations.propTypes = {
86
+ applyTemplate: PropTypes.func,
87
+ i18nContent: PropTypes.object,
88
+ fieldsToOmit: PropTypes.array,
89
+ isModification: PropTypes.bool,
90
+ loading: PropTypes.bool,
91
+ onChange: PropTypes.func,
92
+ selectedDomain: PropTypes.object,
93
+ template: PropTypes.object,
94
+ };
95
+
96
+ const makeMapStateToProps = () => {
97
+ const getTemplate = makeGetTemplate();
98
+ const getApplyTemplate = makeGetApplyTemplate();
99
+
100
+ const mapStateToProps = (state, props) => ({
101
+ applyTemplate: getApplyTemplate(state, props),
102
+ loading: state.loading,
103
+ selectedDomain:
104
+ props.selectedDomain ||
105
+ (!_.isEmpty(state.selectedDomain)
106
+ ? state.selectedDomain
107
+ : _.isNil(_.head(state.selectedDomains))
108
+ ? null
109
+ : { id: _.head(state.selectedDomains) }),
110
+ template: getTemplate(state, props),
111
+ });
112
+ return mapStateToProps;
113
+ };
114
+
115
+ export default connect(makeMapStateToProps)(DynamicFormWithTranslations);
@@ -0,0 +1,200 @@
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+ import { Segment, Header, Grid, Tab } from "semantic-ui-react";
4
+ import { useIntl } from "react-intl";
5
+ import { useLanguage, I18nProvider } from "@truedat/core/i18n";
6
+ import { LanguagesTabs } from "@truedat/core/components";
7
+ import {
8
+ hasTranslatableFields,
9
+ isTranslatableField,
10
+ } from "@truedat/core/services/i18nContent";
11
+ import DynamicField from "./widgets/DynamicField";
12
+ import FieldGroupSubSegment from "./FieldGroupSubSegment/FieldGroupSubSegment";
13
+
14
+ export const FieldGroupWithTranslations = ({
15
+ scope,
16
+ name: groupName,
17
+ fieldsByLang,
18
+ isModification,
19
+ onFieldChange,
20
+ }) => {
21
+ const { formatMessage } = useIntl();
22
+ const {
23
+ defaultLang,
24
+ altLang,
25
+ altLangs,
26
+ requiredLangs,
27
+ setAltLang,
28
+ isMultilingual,
29
+ } = useLanguage();
30
+ const hasTranslations = hasTranslatableFields(
31
+ fieldsByLang[defaultLang] || []
32
+ );
33
+
34
+ const getFieldForLang = (field, lang) => {
35
+ const langFields = fieldsByLang[lang] || [];
36
+ const langField = langFields.find((f) => f.name === field.name);
37
+
38
+ if (!langField) {
39
+ return {
40
+ ...field,
41
+ value: { value: "", origin: "default" },
42
+ };
43
+ }
44
+
45
+ return {
46
+ ...langField,
47
+ value:
48
+ langField.value === ""
49
+ ? ""
50
+ : !langField.value?.value
51
+ ? { value: "", origin: "default" }
52
+ : langField.value,
53
+ };
54
+ };
55
+
56
+ return (
57
+ <>
58
+ {groupName && groupName !== "undefined" && (
59
+ <Header as="h4" content={groupName} />
60
+ )}
61
+ {hasTranslations && isMultilingual ? (
62
+ <>
63
+ <Grid>
64
+ <Grid.Row columns={2}>
65
+ <Grid.Column>
66
+ <Tab
67
+ className="attached-tab"
68
+ attached="top"
69
+ panes={[
70
+ {
71
+ menuItem: formatMessage({
72
+ id: `i18n.messages.locale.${defaultLang}`,
73
+ defaultMessage: defaultLang,
74
+ }),
75
+ },
76
+ ]}
77
+ activeIndex={0}
78
+ />
79
+ </Grid.Column>
80
+ <Grid.Column>
81
+ <LanguagesTabs
82
+ altLangs={altLangs}
83
+ requiredLangs={requiredLangs}
84
+ selectedLang={altLang}
85
+ onLangChange={setAltLang}
86
+ />
87
+ </Grid.Column>
88
+ </Grid.Row>
89
+ </Grid>
90
+ <Segment attached="bottom" className="attached-segment">
91
+ <Grid>
92
+ {fieldsByLang[defaultLang] &&
93
+ fieldsByLang[defaultLang].map((field, i) => (
94
+ <Grid.Row columns={2} key={i}>
95
+ <Grid.Column>
96
+ {field.widget === "copy" ? (
97
+ <FieldGroupSubSegment
98
+ onFieldChange={onFieldChange}
99
+ name={field.name}
100
+ fields={field.value.value}
101
+ prevFields={fieldsByLang[defaultLang]}
102
+ />
103
+ ) : (
104
+ <I18nProvider lang={defaultLang}>
105
+ <DynamicField
106
+ scope={scope}
107
+ field={{
108
+ ...getFieldForLang(field, defaultLang),
109
+ disabled: false,
110
+ }}
111
+ fields={fieldsByLang[defaultLang]}
112
+ isModification={isModification}
113
+ key={`${field.name}-${defaultLang}`}
114
+ lang={defaultLang}
115
+ onChange={(e, { name, value }) =>
116
+ onFieldChange(e, {
117
+ lang: defaultLang,
118
+ name,
119
+ value,
120
+ })
121
+ }
122
+ />
123
+ </I18nProvider>
124
+ )}
125
+ </Grid.Column>
126
+
127
+ <Grid.Column>
128
+ {isTranslatableField(field) && fieldsByLang[altLang] ? (
129
+ field.widget === "copy" ? (
130
+ <FieldGroupSubSegment
131
+ onFieldChange={onFieldChange}
132
+ name={field.name}
133
+ fields={field.value.value}
134
+ prevFields={fieldsByLang[altLang]}
135
+ />
136
+ ) : (
137
+ <I18nProvider lang={altLang}>
138
+ <DynamicField
139
+ scope={scope}
140
+ field={getFieldForLang(field, altLang)}
141
+ fields={fieldsByLang[altLang]}
142
+ isModification={isModification}
143
+ key={`${field.name}-${altLang}`}
144
+ onChange={(e, { name, value }) =>
145
+ onFieldChange(e, {
146
+ lang: altLang,
147
+ name,
148
+ value,
149
+ })
150
+ }
151
+ />
152
+ </I18nProvider>
153
+ )
154
+ ) : null}
155
+ </Grid.Column>
156
+ </Grid.Row>
157
+ ))}
158
+ </Grid>
159
+ </Segment>
160
+ </>
161
+ ) : (
162
+ <Grid>
163
+ <Grid.Row columns={isMultilingual ? 2 : 1}>
164
+ <Grid.Column>
165
+ <Segment>
166
+ {fieldsByLang[defaultLang] &&
167
+ fieldsByLang[defaultLang].map((field, i) => (
168
+ <DynamicField
169
+ key={i}
170
+ scope={scope}
171
+ field={{
172
+ ...getFieldForLang(field, defaultLang),
173
+ disabled: false,
174
+ }}
175
+ fields={fieldsByLang[defaultLang]}
176
+ isModification={isModification}
177
+ lang={defaultLang}
178
+ onChange={(e, { name, value }) =>
179
+ onFieldChange(e, { lang: defaultLang, name, value })
180
+ }
181
+ />
182
+ ))}
183
+ </Segment>
184
+ </Grid.Column>
185
+ </Grid.Row>
186
+ </Grid>
187
+ )}
188
+ </>
189
+ );
190
+ };
191
+
192
+ FieldGroupWithTranslations.propTypes = {
193
+ fieldsByLang: PropTypes.objectOf(PropTypes.array),
194
+ isModification: PropTypes.bool,
195
+ name: PropTypes.string,
196
+ onFieldChange: PropTypes.func,
197
+ scope: PropTypes.string,
198
+ };
199
+
200
+ export default FieldGroupWithTranslations;
@@ -0,0 +1,137 @@
1
+ import _ from "lodash/fp";
2
+ import React, { useMemo, useState } from "react";
3
+ import PropTypes from "prop-types";
4
+ import { Grid } from "semantic-ui-react";
5
+ import { TemplateSelector } from "@truedat/core/components";
6
+ import { applyTemplate, validateContent } from "@truedat/df/utils";
7
+ import { useLanguage } from "@truedat/core/i18n";
8
+ import DynamicFormWithTranslations from "./DynamicFormWithTranslations";
9
+
10
+ export default function SelectDynamicFormWithTranslations({
11
+ i18nContent,
12
+ onChangeContent,
13
+ domainIds,
14
+ header,
15
+ hideLabel,
16
+ isModification,
17
+ name,
18
+ actionKey,
19
+ onChangeTemplate = () => {},
20
+ label,
21
+ placeholder,
22
+ required,
23
+ scope,
24
+ disabled,
25
+ disableSelector,
26
+ selectedTemplate,
27
+ }) {
28
+ const [thisTemplate, setThisTemplate] = useState();
29
+ const [showSelector, setShowSelector] = useState(false);
30
+ const template = selectedTemplate || thisTemplate;
31
+ const { defaultLang, enabledLangs, isMultilingual } = useLanguage();
32
+ const domain = useMemo(() => {
33
+ return _.size(domainIds) > 0 ? { id: _.head(domainIds) } : null;
34
+ }, [domainIds]);
35
+
36
+ const handleChange = (content, currentTemplate = thisTemplate, lang) => {
37
+ onChangeContent(lang, {
38
+ content,
39
+ valid: validateContent(currentTemplate)(content),
40
+ });
41
+ };
42
+
43
+ const handleLoad = ({ templates }) => {
44
+ const templateFound = name
45
+ ? _.find(_.propEq("name", name))(templates)
46
+ : _.size(templates) === 1
47
+ ? _.head(templates)
48
+ : null;
49
+
50
+ setShowSelector(templates.length > 1);
51
+
52
+ if (templateFound && (!name || templateFound.name === name)) {
53
+ onChangeTemplate(templateFound);
54
+ setThisTemplate(templateFound);
55
+
56
+ enabledLangs.forEach((lang) => {
57
+ const contentForLang = applyTemplate(templateFound)(
58
+ i18nContent[lang]?.content || {},
59
+ domain?.id
60
+ );
61
+ handleChange(contentForLang, templateFound, defaultLang);
62
+ });
63
+ }
64
+ };
65
+
66
+ const handleSelected = (_e, { template: selectedTpl }) => {
67
+ if (selectedTpl !== thisTemplate) {
68
+ onChangeTemplate(selectedTpl);
69
+ setThisTemplate(selectedTpl);
70
+
71
+ enabledLangs.forEach((lang) => {
72
+ const languageContent = i18nContent[lang]?.content || {};
73
+ const processedContent = selectedTpl
74
+ ? applyTemplate(selectedTpl)(languageContent, domain?.id)
75
+ : {};
76
+ handleChange(processedContent, selectedTpl, defaultLang);
77
+ });
78
+ }
79
+ };
80
+
81
+ return (
82
+ <>
83
+ {(!actionKey || actionKey === "create") && (
84
+ <Grid style={{ display: showSelector ? "block" : "none" }}>
85
+ <Grid.Row columns={isMultilingual ? 2 : 1}>
86
+ <Grid.Column>
87
+ <TemplateSelector
88
+ scope={scope}
89
+ domainIds={domainIds}
90
+ hideLabel={hideLabel}
91
+ onChange={handleSelected}
92
+ onLoad={handleLoad}
93
+ label={label}
94
+ placeholder={placeholder}
95
+ required={required}
96
+ selectedValue={template?.id}
97
+ disabled={disabled || disableSelector}
98
+ />
99
+ </Grid.Column>
100
+ {isMultilingual && <Grid.Column />}
101
+ </Grid.Row>
102
+ </Grid>
103
+ )}
104
+ {!disabled && template && (
105
+ <>
106
+ {header}
107
+ <DynamicFormWithTranslations
108
+ template={template}
109
+ i18nContent={i18nContent}
110
+ onChange={onChangeContent}
111
+ selectedDomain={domain}
112
+ isModification={isModification}
113
+ />
114
+ </>
115
+ )}
116
+ </>
117
+ );
118
+ }
119
+
120
+ SelectDynamicFormWithTranslations.propTypes = {
121
+ i18nContent: PropTypes.object,
122
+ onChangeContent: PropTypes.func,
123
+ domainIds: PropTypes.array,
124
+ header: PropTypes.node,
125
+ hideLabel: PropTypes.bool,
126
+ isModification: PropTypes.bool,
127
+ name: PropTypes.string,
128
+ actionKey: PropTypes.string,
129
+ onChangeTemplate: PropTypes.func,
130
+ label: PropTypes.string,
131
+ placeholder: PropTypes.string,
132
+ required: PropTypes.bool,
133
+ scope: PropTypes.string,
134
+ disabled: PropTypes.bool,
135
+ disableSelector: PropTypes.bool,
136
+ selectedTemplate: PropTypes.object,
137
+ };
@@ -14,7 +14,7 @@ export const DateField = ({ field: { name, value }, onChange }) => {
14
14
  placeholder="Date"
15
15
  value={_.isEmpty(value) ? "" : value}
16
16
  iconPosition="left"
17
- onChange={(e, { value }) => onChange(e, { name: name, value: value })}
17
+ onChange={(_e, { value }) => onChange(null, { name: name, value: value })}
18
18
  />
19
19
  );
20
20
  };
@@ -57,5 +57,18 @@
57
57
  font-size: medium;
58
58
  }
59
59
  .default-value {
60
- whiteSpace: "pre-wrap"
60
+ white-space: pre-wrap;
61
+ }
62
+
63
+ .attached-tab.ui.menu.attached.top {
64
+ border: 1px solid #ddd;
65
+ border-radius: 4px 4px 0 0;
66
+ margin: 0;
67
+ }
68
+
69
+ .attached-segment.ui.attached.segment.bottom {
70
+ border: 1px solid #ddd;
71
+ border-top: none;
72
+ border-radius: 0 0 4px 4px;
73
+ padding: 1em;
61
74
  }
@@ -13,4 +13,4 @@ export {
13
13
  validateRequired,
14
14
  } from "./validateContent";
15
15
  export { parseFieldOptions } from "./parseFieldOptions";
16
- export { parseGroups } from "./parseGroups";
16
+ export { parseGroups, parseGroupsWithLangs } from "./parseGroups";
@@ -41,3 +41,51 @@ export const parseGroups =
41
41
  }),
42
42
  _.filter(({ fields }) => _.negate(_.isEmpty)(fields))
43
43
  )(template);
44
+
45
+ export const parseGroupsWithLangs =
46
+ (formatMessage) =>
47
+ (template, i18nContent, fieldsToOmit, selectedDomain, requiredLangs = []) => {
48
+ return _.flow(
49
+ _.getOr([], "content"),
50
+ _.map((group) => {
51
+ const langs = _.flow(
52
+ _.toPairs,
53
+ _.map(([lang, langContent]) => {
54
+ const fields = _.flow(
55
+ _.get("fields"),
56
+ _.reject((field) => _.includes(fieldsToOmit)(field?.name)),
57
+ _.filter((field) => {
58
+ return (
59
+ (!("depends" in field) ||
60
+ (hasDependentKeys(field) &&
61
+ checkDependency(field, langContent))) &&
62
+ (!(field.values && "switch" in field.values) ||
63
+ _.prop(`${_.prop("values.switch.on")(field)}.value`)(
64
+ langContent
65
+ ) in _.prop("values.switch.values")(field))
66
+ );
67
+ }),
68
+ _.map((field) => ({
69
+ ...field,
70
+ ...parseFieldOptions(formatMessage)(
71
+ langContent,
72
+ selectedDomain
73
+ )(field),
74
+ ...(requiredLangs.includes(lang)
75
+ ? enrichRequired(langContent)(field)
76
+ : { required: false }),
77
+ ...enrichIsSecret(_.get("is_secret")(group))(field),
78
+ }))
79
+ )(group);
80
+ return [lang, fields];
81
+ }),
82
+ _.fromPairs
83
+ )(i18nContent);
84
+ return {
85
+ name: group.name,
86
+ langs,
87
+ };
88
+ }),
89
+ _.filter(({ langs }) => _.some((fields) => !_.isEmpty(fields))(langs))
90
+ )(template);
91
+ };