@truedat/df 8.13.5 → 8.13.7
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 +4 -4
- package/src/components/DynamicForm.js +3 -0
- package/src/components/DynamicFormWithTranslations.js +3 -0
- package/src/components/FieldGroupSegment.js +3 -0
- package/src/components/FieldGroupWithTranslations.js +13 -0
- package/src/components/SelectDynamicFormWithTranslations.js +7 -1
- package/src/components/SelectableDynamicForm.js +7 -1
- package/src/components/__tests__/__snapshots__/SelectableDynamicForm.spec.js.snap +18 -1
- package/src/components/__tests__/originalContentFlow.spec.js +131 -0
- package/src/components/widgets/DateField.js +18 -9
- package/src/components/widgets/DateTimeField.js +26 -9
- package/src/components/widgets/DynamicField.js +54 -1
- package/src/components/widgets/DynamicTableField.js +41 -7
- package/src/components/widgets/FieldByWidget.js +36 -6
- package/src/components/widgets/__tests__/dateBoundsByWidget.spec.js +133 -0
- package/src/components/widgets/__tests__/dateBoundsEndToEnd.spec.js +83 -0
- package/src/components/widgets/__tests__/datePickerBoundaries.spec.js +124 -0
- package/src/components/widgets/__tests__/dateValueValidation.spec.js +215 -0
- package/src/components/widgets/__tests__/existingDateRestrictions.spec.js +354 -0
- package/src/templates/components/templateForm/DateRestrictionsForm.js +217 -0
- package/src/templates/components/templateForm/DefaultValue.js +79 -11
- package/src/templates/components/templateForm/FieldDefinition.js +15 -1
- package/src/templates/components/templateForm/TemplateForm.js +5 -0
- package/src/templates/components/templateForm/ValuesField.js +7 -0
- package/src/templates/components/templateForm/__tests__/FieldDefinition.spec.js +48 -0
- package/src/templates/components/templateForm/__tests__/TemplateForm.spec.js +107 -0
- package/src/templates/components/templateForm/__tests__/dateRestrictionsForm.spec.js +429 -0
- package/src/templates/components/templateForm/__tests__/dateRestrictionsValidation.spec.js +166 -0
- package/src/templates/components/templateForm/__tests__/defaultValueBounds.spec.js +191 -0
- package/src/templates/components/templateForm/__tests__/defaultValueRestrictions.spec.js +175 -0
- package/src/templates/components/templateForm/contentValidation.js +184 -0
- package/src/templates/utils/__tests__/dateRestrictions.spec.js +578 -0
- package/src/templates/utils/__tests__/originalTableRows.spec.js +129 -0
- package/src/templates/utils/__tests__/validateContentDateRestrictions.spec.js +519 -0
- package/src/templates/utils/applyTemplate.js +27 -20
- package/src/templates/utils/dateRestrictions.js +497 -0
- package/src/templates/utils/dateValueUnchanged.js +12 -0
- package/src/templates/utils/originalTableRows.js +41 -0
- package/src/templates/utils/validateContent.js +75 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/df",
|
|
3
|
-
"version": "8.13.
|
|
3
|
+
"version": "8.13.7",
|
|
4
4
|
"description": "Truedat Web Data Quality Module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -54,14 +54,14 @@
|
|
|
54
54
|
"@testing-library/jest-dom": "^6.6.3",
|
|
55
55
|
"@testing-library/react": "^16.3.0",
|
|
56
56
|
"@testing-library/user-event": "^14.6.1",
|
|
57
|
-
"@truedat/test": "8.13.
|
|
57
|
+
"@truedat/test": "8.13.7",
|
|
58
58
|
"identity-obj-proxy": "^3.0.0",
|
|
59
59
|
"jest": "^29.7.0",
|
|
60
60
|
"redux-saga-test-plan": "^4.0.6"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@apollo/client": "^3.13.8",
|
|
64
|
-
"@truedat/core": "8.13.
|
|
64
|
+
"@truedat/core": "8.13.7",
|
|
65
65
|
"axios": "^1.20.0",
|
|
66
66
|
"graphql": "^16.11.0",
|
|
67
67
|
"is-hotkey": "^0.2.0",
|
|
@@ -90,5 +90,5 @@
|
|
|
90
90
|
"semantic-ui-react": "^3.0.0-beta.2",
|
|
91
91
|
"swr": "^2.3.3"
|
|
92
92
|
},
|
|
93
|
-
"gitHead": "
|
|
93
|
+
"gitHead": "704dcfcba34b6094a368a93059de7f4937502f32"
|
|
94
94
|
}
|
|
@@ -10,6 +10,7 @@ import FieldGroupSegment from "./FieldGroupSegment";
|
|
|
10
10
|
export const DynamicForm = ({
|
|
11
11
|
applyTemplate,
|
|
12
12
|
content,
|
|
13
|
+
originalContent,
|
|
13
14
|
fieldsToOmit,
|
|
14
15
|
instanceId,
|
|
15
16
|
isModification,
|
|
@@ -98,6 +99,7 @@ export const DynamicForm = ({
|
|
|
98
99
|
noTranslatableFields={noTranslatableFields}
|
|
99
100
|
scope={template?.scope}
|
|
100
101
|
fields={fields}
|
|
102
|
+
originalContent={originalContent}
|
|
101
103
|
onFieldChange={handleChange}
|
|
102
104
|
isModification={isModification}
|
|
103
105
|
/>
|
|
@@ -107,6 +109,7 @@ export const DynamicForm = ({
|
|
|
107
109
|
DynamicForm.propTypes = {
|
|
108
110
|
applyTemplate: PropTypes.func,
|
|
109
111
|
content: PropTypes.object,
|
|
112
|
+
originalContent: PropTypes.object,
|
|
110
113
|
fieldsToOmit: PropTypes.array,
|
|
111
114
|
instanceId: PropTypes.string,
|
|
112
115
|
isModification: PropTypes.bool,
|
|
@@ -11,6 +11,7 @@ import FieldGroupWithTranslations from "./FieldGroupWithTranslations";
|
|
|
11
11
|
export const DynamicFormWithTranslations = ({
|
|
12
12
|
applyTemplate,
|
|
13
13
|
i18nContent,
|
|
14
|
+
originalI18nContent,
|
|
14
15
|
fieldsToOmit,
|
|
15
16
|
isModification,
|
|
16
17
|
loading,
|
|
@@ -76,6 +77,7 @@ export const DynamicFormWithTranslations = ({
|
|
|
76
77
|
}
|
|
77
78
|
scope={template?.scope}
|
|
78
79
|
fieldsByLang={langs}
|
|
80
|
+
originalI18nContent={originalI18nContent}
|
|
79
81
|
onFieldChange={handleChange}
|
|
80
82
|
isModification={isModification}
|
|
81
83
|
/>
|
|
@@ -85,6 +87,7 @@ export const DynamicFormWithTranslations = ({
|
|
|
85
87
|
DynamicFormWithTranslations.propTypes = {
|
|
86
88
|
applyTemplate: PropTypes.func,
|
|
87
89
|
i18nContent: PropTypes.object,
|
|
90
|
+
originalI18nContent: PropTypes.object,
|
|
88
91
|
fieldsToOmit: PropTypes.array,
|
|
89
92
|
isModification: PropTypes.bool,
|
|
90
93
|
loading: PropTypes.bool,
|
|
@@ -7,6 +7,7 @@ export const FieldGroupSegment = ({
|
|
|
7
7
|
instanceId,
|
|
8
8
|
scope,
|
|
9
9
|
fields,
|
|
10
|
+
originalContent,
|
|
10
11
|
isModification,
|
|
11
12
|
name,
|
|
12
13
|
noTranslatableFields,
|
|
@@ -24,6 +25,7 @@ export const FieldGroupSegment = ({
|
|
|
24
25
|
disabled: _.includes(field.name)(noTranslatableFields),
|
|
25
26
|
}}
|
|
26
27
|
fields={fields}
|
|
28
|
+
originalField={originalContent?.[field.name]}
|
|
27
29
|
isModification={isModification}
|
|
28
30
|
key={i}
|
|
29
31
|
onChange={
|
|
@@ -38,6 +40,7 @@ export const FieldGroupSegment = ({
|
|
|
38
40
|
|
|
39
41
|
FieldGroupSegment.propTypes = {
|
|
40
42
|
fields: PropTypes.array,
|
|
43
|
+
originalContent: PropTypes.object,
|
|
41
44
|
instanceId: PropTypes.string,
|
|
42
45
|
isModification: PropTypes.bool,
|
|
43
46
|
name: PropTypes.string,
|
|
@@ -13,6 +13,7 @@ export const FieldGroupWithTranslations = ({
|
|
|
13
13
|
scope,
|
|
14
14
|
name: groupName,
|
|
15
15
|
fieldsByLang,
|
|
16
|
+
originalI18nContent,
|
|
16
17
|
isModification,
|
|
17
18
|
onFieldChange,
|
|
18
19
|
}) => {
|
|
@@ -101,6 +102,9 @@ export const FieldGroupWithTranslations = ({
|
|
|
101
102
|
disabled: false,
|
|
102
103
|
}}
|
|
103
104
|
fields={fieldsByLang[defaultLang]}
|
|
105
|
+
originalField={
|
|
106
|
+
originalI18nContent?.[defaultLang]?.[field.name]
|
|
107
|
+
}
|
|
104
108
|
isModification={isModification}
|
|
105
109
|
key={`${field.name}-${defaultLang}`}
|
|
106
110
|
lang={defaultLang}
|
|
@@ -122,6 +126,11 @@ export const FieldGroupWithTranslations = ({
|
|
|
122
126
|
scope={scope}
|
|
123
127
|
field={getFieldForLang(field, selectedAltLang)}
|
|
124
128
|
fields={fieldsByLang[selectedAltLang] || []}
|
|
129
|
+
originalField={
|
|
130
|
+
originalI18nContent?.[selectedAltLang]?.[
|
|
131
|
+
field.name
|
|
132
|
+
]
|
|
133
|
+
}
|
|
125
134
|
isModification={isModification}
|
|
126
135
|
key={`${field.name}-${selectedAltLang}`}
|
|
127
136
|
onChange={(e, { name, value }) =>
|
|
@@ -155,6 +164,9 @@ export const FieldGroupWithTranslations = ({
|
|
|
155
164
|
disabled: false,
|
|
156
165
|
}}
|
|
157
166
|
fields={fieldsByLang[defaultLang]}
|
|
167
|
+
originalField={
|
|
168
|
+
originalI18nContent?.[defaultLang]?.[field.name]
|
|
169
|
+
}
|
|
158
170
|
isModification={isModification}
|
|
159
171
|
lang={defaultLang}
|
|
160
172
|
onChange={(e, { name, value }) =>
|
|
@@ -173,6 +185,7 @@ export const FieldGroupWithTranslations = ({
|
|
|
173
185
|
|
|
174
186
|
FieldGroupWithTranslations.propTypes = {
|
|
175
187
|
fieldsByLang: PropTypes.objectOf(PropTypes.array),
|
|
188
|
+
originalI18nContent: PropTypes.object,
|
|
176
189
|
isModification: PropTypes.bool,
|
|
177
190
|
name: PropTypes.string,
|
|
178
191
|
onFieldChange: PropTypes.func,
|
|
@@ -11,6 +11,7 @@ import DynamicFormWithTranslations from "./DynamicFormWithTranslations";
|
|
|
11
11
|
export default function SelectDynamicFormWithTranslations({
|
|
12
12
|
beforeSelector,
|
|
13
13
|
i18nContent,
|
|
14
|
+
originalI18nContent,
|
|
14
15
|
onChangeContent,
|
|
15
16
|
domainIds,
|
|
16
17
|
header,
|
|
@@ -48,7 +49,10 @@ export default function SelectDynamicFormWithTranslations({
|
|
|
48
49
|
const handleChange = (content, currentTemplate = thisTemplate, lang) => {
|
|
49
50
|
onChangeContent(lang, {
|
|
50
51
|
content,
|
|
51
|
-
valid: validateContent(
|
|
52
|
+
valid: validateContent(
|
|
53
|
+
currentTemplate,
|
|
54
|
+
originalI18nContent?.[lang],
|
|
55
|
+
)(content),
|
|
52
56
|
});
|
|
53
57
|
};
|
|
54
58
|
|
|
@@ -124,6 +128,7 @@ export default function SelectDynamicFormWithTranslations({
|
|
|
124
128
|
<DynamicFormWithTranslations
|
|
125
129
|
template={template}
|
|
126
130
|
i18nContent={i18nContent}
|
|
131
|
+
originalI18nContent={originalI18nContent}
|
|
127
132
|
onChange={onChangeContent}
|
|
128
133
|
selectedDomain={domain}
|
|
129
134
|
isModification={isModification}
|
|
@@ -137,6 +142,7 @@ export default function SelectDynamicFormWithTranslations({
|
|
|
137
142
|
SelectDynamicFormWithTranslations.propTypes = {
|
|
138
143
|
beforeSelector: PropTypes.node,
|
|
139
144
|
i18nContent: PropTypes.object,
|
|
145
|
+
originalI18nContent: PropTypes.object,
|
|
140
146
|
onChangeContent: PropTypes.func,
|
|
141
147
|
domainIds: PropTypes.array,
|
|
142
148
|
header: PropTypes.node,
|
|
@@ -23,6 +23,7 @@ const TemplateQueryFetcher = ({ scope, domainIds, onLoad }) => {
|
|
|
23
23
|
|
|
24
24
|
export default function SelectableDynamicForm({
|
|
25
25
|
content,
|
|
26
|
+
originalContent,
|
|
26
27
|
domainIds,
|
|
27
28
|
header,
|
|
28
29
|
hideLabel,
|
|
@@ -59,7 +60,10 @@ export default function SelectableDynamicForm({
|
|
|
59
60
|
: null;
|
|
60
61
|
|
|
61
62
|
const handleChange = (content, currentTemplate = template) => {
|
|
62
|
-
onChange({
|
|
63
|
+
onChange({
|
|
64
|
+
content,
|
|
65
|
+
valid: validateContent(currentTemplate, originalContent)(content),
|
|
66
|
+
});
|
|
63
67
|
};
|
|
64
68
|
|
|
65
69
|
const handleLoad = ({ templates }) => {
|
|
@@ -112,6 +116,7 @@ export default function SelectableDynamicForm({
|
|
|
112
116
|
{header}
|
|
113
117
|
<DynamicForm
|
|
114
118
|
content={content}
|
|
119
|
+
originalContent={originalContent}
|
|
115
120
|
instanceId={instanceId}
|
|
116
121
|
selectedDomain={domain}
|
|
117
122
|
template={template}
|
|
@@ -127,6 +132,7 @@ export default function SelectableDynamicForm({
|
|
|
127
132
|
|
|
128
133
|
SelectableDynamicForm.propTypes = {
|
|
129
134
|
content: PropTypes.object,
|
|
135
|
+
originalContent: PropTypes.object,
|
|
130
136
|
domainIds: PropTypes.array,
|
|
131
137
|
header: PropTypes.node,
|
|
132
138
|
hideLabel: PropTypes.bool,
|
|
@@ -35,7 +35,24 @@ exports[`<SelectableDynamicForm /> with disableSelector and selectedTemplate mat
|
|
|
35
35
|
</div>
|
|
36
36
|
`;
|
|
37
37
|
|
|
38
|
-
exports[`<SelectableDynamicForm /> with multiple templates matches error snapshot 1`] =
|
|
38
|
+
exports[`<SelectableDynamicForm /> with multiple templates matches error snapshot 1`] = `
|
|
39
|
+
<div>
|
|
40
|
+
<div
|
|
41
|
+
class="ui compact negative message"
|
|
42
|
+
>
|
|
43
|
+
<div
|
|
44
|
+
class="content"
|
|
45
|
+
>
|
|
46
|
+
Templates could not be loaded. Check your access and try again.
|
|
47
|
+
<button
|
|
48
|
+
class="ui small button"
|
|
49
|
+
>
|
|
50
|
+
Retry
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
`;
|
|
39
56
|
|
|
40
57
|
exports[`<SelectableDynamicForm /> with multiple templates matches the latest snapshot 1`] = `
|
|
41
58
|
<div>
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { render } from "@truedat/test/render";
|
|
2
|
+
import { DynamicForm } from "../DynamicForm";
|
|
3
|
+
import { DynamicFormWithTranslations } from "../DynamicFormWithTranslations";
|
|
4
|
+
import DynamicField from "../widgets/DynamicField";
|
|
5
|
+
import { parseGroups, parseGroupsWithLangs } from "../../utils";
|
|
6
|
+
|
|
7
|
+
jest.mock("../widgets/DynamicField", () => ({
|
|
8
|
+
__esModule: true,
|
|
9
|
+
default: jest.fn(() => null),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
jest.mock("../../utils", () => ({
|
|
13
|
+
...jest.requireActual("../../utils"),
|
|
14
|
+
parseGroups: jest.fn(),
|
|
15
|
+
parseGroupsWithLangs: jest.fn(),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
jest.mock("@truedat/core/i18n", () => ({
|
|
19
|
+
useLanguage: () => ({
|
|
20
|
+
defaultLang: "en",
|
|
21
|
+
altLang: "es",
|
|
22
|
+
altLangs: ["es"],
|
|
23
|
+
requiredLangs: ["en"],
|
|
24
|
+
isMultilingual: true,
|
|
25
|
+
setAltLang: jest.fn(),
|
|
26
|
+
}),
|
|
27
|
+
I18nProvider: ({ children }) => children,
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
const template = { name: "template", content: [] };
|
|
31
|
+
const originalDate = { value: "2025-01-01", origin: "user" };
|
|
32
|
+
const translatedOriginalDate = { value: "2024-01-01", origin: "user" };
|
|
33
|
+
const field = {
|
|
34
|
+
name: "date",
|
|
35
|
+
label: "Date",
|
|
36
|
+
type: "date",
|
|
37
|
+
widget: "date",
|
|
38
|
+
value: originalDate,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
jest.clearAllMocks();
|
|
43
|
+
parseGroups.mockReturnValue(() => [{ fields: [field] }]);
|
|
44
|
+
parseGroupsWithLangs.mockReturnValue(() => [
|
|
45
|
+
{
|
|
46
|
+
langs: {
|
|
47
|
+
en: [{ ...field, widget: "string" }],
|
|
48
|
+
es: [{ ...field, widget: "string" }],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("DynamicForm original content", () => {
|
|
55
|
+
it("forwards the original field object through its group", () => {
|
|
56
|
+
render(
|
|
57
|
+
<DynamicForm
|
|
58
|
+
template={template}
|
|
59
|
+
content={{ date: originalDate }}
|
|
60
|
+
originalContent={{ date: originalDate }}
|
|
61
|
+
applyTemplate={(content) => content}
|
|
62
|
+
onChange={jest.fn()}
|
|
63
|
+
/>,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(DynamicField.mock.calls[0][0].originalField).toBe(originalDate);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("does not invent a baseline when original content is absent", () => {
|
|
70
|
+
render(
|
|
71
|
+
<DynamicForm
|
|
72
|
+
template={template}
|
|
73
|
+
content={{ date: originalDate }}
|
|
74
|
+
applyTemplate={(content) => content}
|
|
75
|
+
onChange={jest.fn()}
|
|
76
|
+
/>,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
expect(DynamicField.mock.calls[0][0].originalField).toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("DynamicFormWithTranslations original content", () => {
|
|
84
|
+
it("selects each language baseline directly from its content map", () => {
|
|
85
|
+
render(
|
|
86
|
+
<DynamicFormWithTranslations
|
|
87
|
+
template={template}
|
|
88
|
+
i18nContent={{ en: { date: originalDate }, es: {} }}
|
|
89
|
+
originalI18nContent={{
|
|
90
|
+
en: { date: originalDate },
|
|
91
|
+
es: { date: translatedOriginalDate },
|
|
92
|
+
}}
|
|
93
|
+
onChange={jest.fn()}
|
|
94
|
+
/>,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
expect(DynamicField.mock.calls[0][0].originalField).toBe(originalDate);
|
|
98
|
+
expect(DynamicField.mock.calls[1][0].originalField).toBe(
|
|
99
|
+
translatedOriginalDate,
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("does not use the default language baseline for a new translation", () => {
|
|
104
|
+
render(
|
|
105
|
+
<DynamicFormWithTranslations
|
|
106
|
+
template={template}
|
|
107
|
+
i18nContent={{ en: { date: originalDate }, es: {} }}
|
|
108
|
+
originalI18nContent={{ en: { date: originalDate } }}
|
|
109
|
+
onChange={jest.fn()}
|
|
110
|
+
/>,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(DynamicField.mock.calls[1][0].originalField).toBeUndefined();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("forwards the baseline for groups without translatable fields", () => {
|
|
117
|
+
parseGroupsWithLangs.mockReturnValue(() => [
|
|
118
|
+
{ langs: { en: [{ ...field, type: "number", widget: "number" }] } },
|
|
119
|
+
]);
|
|
120
|
+
render(
|
|
121
|
+
<DynamicFormWithTranslations
|
|
122
|
+
template={template}
|
|
123
|
+
i18nContent={{ en: { date: originalDate } }}
|
|
124
|
+
originalI18nContent={{ en: { date: originalDate } }}
|
|
125
|
+
onChange={jest.fn()}
|
|
126
|
+
/>,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
expect(DynamicField.mock.calls[0][0].originalField).toBe(originalDate);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -1,19 +1,28 @@
|
|
|
1
|
-
import _ from "lodash/fp";
|
|
2
1
|
import PropTypes from "prop-types";
|
|
3
2
|
import { Form } from "semantic-ui-react";
|
|
3
|
+
import { checkValuesAgainstBounds } from "../../templates/utils/dateRestrictions";
|
|
4
4
|
|
|
5
|
-
export const DateField = ({ field: { name, value }, onChange }) =>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
)
|
|
5
|
+
export const DateField = ({ field: { name, value }, onChange, min, max }) => {
|
|
6
|
+
const outOfRange = checkValuesAgainstBounds(value, { min, max }) !== "ok";
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<Form.Input
|
|
10
|
+
error={outOfRange}
|
|
11
|
+
placeholder="Date"
|
|
12
|
+
onChange={(e, { value }) => onChange(e, { name: name, value: value })}
|
|
13
|
+
value={value || ""}
|
|
14
|
+
min={min}
|
|
15
|
+
max={max}
|
|
16
|
+
type="date"
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
13
20
|
|
|
14
21
|
DateField.propTypes = {
|
|
15
22
|
field: PropTypes.object,
|
|
16
23
|
onChange: PropTypes.func,
|
|
24
|
+
min: PropTypes.string,
|
|
25
|
+
max: PropTypes.string,
|
|
17
26
|
};
|
|
18
27
|
|
|
19
28
|
export default DateField;
|
|
@@ -1,19 +1,36 @@
|
|
|
1
|
-
import _ from "lodash/fp";
|
|
2
1
|
import PropTypes from "prop-types";
|
|
3
2
|
import { Form } from "semantic-ui-react";
|
|
3
|
+
import {
|
|
4
|
+
checkValuesAgainstBounds,
|
|
5
|
+
toInputBound,
|
|
6
|
+
} from "../../templates/utils/dateRestrictions";
|
|
4
7
|
|
|
5
|
-
export const DateTimeField = ({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
export const DateTimeField = ({
|
|
9
|
+
field: { name, value } = {},
|
|
10
|
+
onChange,
|
|
11
|
+
min,
|
|
12
|
+
max,
|
|
13
|
+
}) => {
|
|
14
|
+
const outOfRange = checkValuesAgainstBounds(value, { min, max }) !== "ok";
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Form.Input
|
|
18
|
+
error={outOfRange}
|
|
19
|
+
placeholder="Date Time"
|
|
20
|
+
onChange={(e, { value }) => onChange(e, { name: name, value: value })}
|
|
21
|
+
value={value || ""}
|
|
22
|
+
min={toInputBound(min, "min")}
|
|
23
|
+
max={toInputBound(max, "max")}
|
|
24
|
+
type="datetime-local"
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
13
28
|
|
|
14
29
|
DateTimeField.propTypes = {
|
|
15
30
|
field: PropTypes.object,
|
|
16
31
|
onChange: PropTypes.func,
|
|
32
|
+
min: PropTypes.string,
|
|
33
|
+
max: PropTypes.string,
|
|
17
34
|
};
|
|
18
35
|
|
|
19
36
|
export default DateTimeField;
|
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
import _ from "lodash/fp";
|
|
2
2
|
import PropTypes from "prop-types";
|
|
3
3
|
import { Form, Icon, Popup, Label } from "semantic-ui-react";
|
|
4
|
-
import { FormattedMessage } from "react-intl";
|
|
4
|
+
import { FormattedMessage, useIntl } from "react-intl";
|
|
5
5
|
import { MarkdownReader } from "@truedat/core/components/MarkdownReader";
|
|
6
6
|
import OriginLabel from "@truedat/core/components/OriginLabel";
|
|
7
|
+
import { dateValueUnchanged } from "../../templates/utils/dateValueUnchanged";
|
|
8
|
+
import {
|
|
9
|
+
publishedBounds,
|
|
10
|
+
checkValuesAgainstBounds,
|
|
11
|
+
resolveRestrictions,
|
|
12
|
+
} from "../../templates/utils/dateRestrictions";
|
|
7
13
|
import FieldByWidget from "./FieldByWidget";
|
|
8
14
|
|
|
9
15
|
const isNullOrEmpty = (value) =>
|
|
10
16
|
!_.isNumber(value) && (!value || _.isEmpty(value));
|
|
11
17
|
|
|
18
|
+
const dateBounds = (field, type) => {
|
|
19
|
+
if (type !== "date" && type !== "datetime") return {};
|
|
20
|
+
return (
|
|
21
|
+
publishedBounds(field?.restrictions) ||
|
|
22
|
+
resolveRestrictions(field?.restrictions, type)
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
12
26
|
export const DynamicField = ({
|
|
13
27
|
field: {
|
|
14
28
|
label,
|
|
@@ -21,6 +35,7 @@ export const DynamicField = ({
|
|
|
21
35
|
},
|
|
22
36
|
instanceId,
|
|
23
37
|
isModification,
|
|
38
|
+
originalField,
|
|
24
39
|
onChange,
|
|
25
40
|
scope,
|
|
26
41
|
}) => {
|
|
@@ -28,8 +43,21 @@ export const DynamicField = ({
|
|
|
28
43
|
value: null,
|
|
29
44
|
origin: "default",
|
|
30
45
|
};
|
|
46
|
+
|
|
47
|
+
const { formatMessage } = useIntl();
|
|
48
|
+
|
|
49
|
+
const unchanged = dateValueUnchanged(value, originalField);
|
|
50
|
+
const bounds = unchanged ? {} : dateBounds(fieldProps, fieldProps.type);
|
|
51
|
+
const verdict =
|
|
52
|
+
fieldProps.type === "date" || fieldProps.type === "datetime"
|
|
53
|
+
? checkValuesAgainstBounds(value, bounds)
|
|
54
|
+
: "ok";
|
|
55
|
+
const outOfRange = verdict !== "ok";
|
|
56
|
+
const breachedBound = verdict === "too_early" ? bounds.min : bounds.max;
|
|
57
|
+
|
|
31
58
|
return (
|
|
32
59
|
<Form.Field
|
|
60
|
+
error={outOfRange}
|
|
33
61
|
disabled={(!editable && isModification) || disabled}
|
|
34
62
|
data-testid="form-field"
|
|
35
63
|
>
|
|
@@ -89,10 +117,34 @@ export const DynamicField = ({
|
|
|
89
117
|
</label>
|
|
90
118
|
<FieldByWidget
|
|
91
119
|
field={{ ...fieldProps, value, cardinality, instanceId }}
|
|
120
|
+
originalField={originalField}
|
|
92
121
|
onChange={onChange}
|
|
93
122
|
scope={scope}
|
|
94
123
|
widget={_.prop("widget")(fieldProps)}
|
|
95
124
|
/>
|
|
125
|
+
{outOfRange ? (
|
|
126
|
+
<Label
|
|
127
|
+
basic
|
|
128
|
+
color="red"
|
|
129
|
+
pointing="above"
|
|
130
|
+
style={{ marginTop: "0.5em" }}
|
|
131
|
+
>
|
|
132
|
+
{verdict === "invalid_date"
|
|
133
|
+
? formatMessage({
|
|
134
|
+
id: "template.field.restrictions.validation.invalid_date",
|
|
135
|
+
defaultMessage: "Enter a valid date or date-time.",
|
|
136
|
+
})
|
|
137
|
+
: formatMessage(
|
|
138
|
+
{
|
|
139
|
+
id:
|
|
140
|
+
verdict === "too_early"
|
|
141
|
+
? "template.field.values.default_after_min"
|
|
142
|
+
: "template.field.values.default_before_max",
|
|
143
|
+
},
|
|
144
|
+
{ limit: breachedBound },
|
|
145
|
+
)}
|
|
146
|
+
</Label>
|
|
147
|
+
) : null}
|
|
96
148
|
</Form.Field>
|
|
97
149
|
);
|
|
98
150
|
};
|
|
@@ -101,6 +153,7 @@ DynamicField.propTypes = {
|
|
|
101
153
|
field: PropTypes.object.isRequired,
|
|
102
154
|
instanceId: PropTypes.string,
|
|
103
155
|
isModification: PropTypes.bool,
|
|
156
|
+
originalField: PropTypes.object,
|
|
104
157
|
onChange: PropTypes.func.isRequired,
|
|
105
158
|
scope: PropTypes.string,
|
|
106
159
|
};
|
|
@@ -3,39 +3,53 @@ import { lazy } from "react";
|
|
|
3
3
|
import PropTypes from "prop-types";
|
|
4
4
|
import { Button, Icon, Label, Table } from "semantic-ui-react";
|
|
5
5
|
import { useIntl } from "react-intl";
|
|
6
|
+
import {
|
|
7
|
+
copyRow,
|
|
8
|
+
getOriginalRow,
|
|
9
|
+
markNewRow,
|
|
10
|
+
} from "../../templates/utils/originalTableRows";
|
|
11
|
+
import { dateValueUnchanged } from "../../templates/utils/dateValueUnchanged";
|
|
12
|
+
import {
|
|
13
|
+
publishedBounds,
|
|
14
|
+
checkValuesAgainstBounds,
|
|
15
|
+
resolveRestrictions,
|
|
16
|
+
} from "../../templates/utils/dateRestrictions";
|
|
6
17
|
|
|
7
18
|
const FieldByWidget = lazy(() => import("./FieldByWidget"));
|
|
8
19
|
|
|
9
20
|
export const DynamicTableField = ({
|
|
10
21
|
field: { name, value: fieldValue = [], values },
|
|
22
|
+
originalField,
|
|
11
23
|
onChange,
|
|
12
24
|
scope,
|
|
13
25
|
}) => {
|
|
14
26
|
const value = fieldValue || [];
|
|
15
27
|
const table_columns = _.get("table_columns")(values) || [];
|
|
16
28
|
const { formatMessage } = useIntl();
|
|
29
|
+
const originalRows = value.map((row, index) =>
|
|
30
|
+
getOriginalRow(row, index, originalField?.value),
|
|
31
|
+
);
|
|
17
32
|
|
|
18
33
|
const handleAddRow = (e) => {
|
|
19
34
|
e && e.preventDefault();
|
|
20
35
|
onChange(e, {
|
|
21
36
|
name,
|
|
22
|
-
value: [...value, {}],
|
|
37
|
+
value: [...value, markNewRow({})],
|
|
23
38
|
});
|
|
24
39
|
};
|
|
25
40
|
|
|
26
41
|
const handleRemoveRow = (e, id) => {
|
|
27
42
|
e && e.preventDefault();
|
|
28
|
-
const newValue =
|
|
29
|
-
newValue.splice(id, 1);
|
|
43
|
+
const newValue = value.filter((_row, index) => index !== id);
|
|
30
44
|
onChange(e, { name, value: newValue });
|
|
31
45
|
};
|
|
32
46
|
|
|
33
47
|
const handleChange = (idx, e, data) => {
|
|
34
48
|
const currentValue = removeDeletedColumns(table_columns, [...value]);
|
|
35
|
-
const input = {
|
|
49
|
+
const input = copyRow(currentValue[idx], {
|
|
36
50
|
...currentValue[idx],
|
|
37
51
|
[data.name]: { origin: "user", value: data.value },
|
|
38
|
-
};
|
|
52
|
+
});
|
|
39
53
|
onChange(e, {
|
|
40
54
|
name,
|
|
41
55
|
value: currentValue.map((newElem, i) => (i == idx ? input : newElem)),
|
|
@@ -48,7 +62,7 @@ export const DynamicTableField = ({
|
|
|
48
62
|
const field_deleted = _.keys(row).filter(
|
|
49
63
|
(x) => !columns_name.includes(x),
|
|
50
64
|
);
|
|
51
|
-
return _.omit(field_deleted)(row);
|
|
65
|
+
return copyRow(row, _.omit(field_deleted)(row));
|
|
52
66
|
});
|
|
53
67
|
};
|
|
54
68
|
|
|
@@ -70,6 +84,20 @@ export const DynamicTableField = ({
|
|
|
70
84
|
_.fromPairs,
|
|
71
85
|
)(rows);
|
|
72
86
|
|
|
87
|
+
const originalCell = (rowIndex, fieldName) =>
|
|
88
|
+
originalRows[rowIndex]?.[fieldName];
|
|
89
|
+
|
|
90
|
+
const dateError = (field, originalCell) => {
|
|
91
|
+
const unchanged = dateValueUnchanged(field.value, originalCell);
|
|
92
|
+
if (unchanged || !["date", "datetime"].includes(field?.type)) return false;
|
|
93
|
+
const bounds =
|
|
94
|
+
publishedBounds(field?.restrictions) ||
|
|
95
|
+
resolveRestrictions(field?.restrictions, field?.type);
|
|
96
|
+
return bounds
|
|
97
|
+
? checkValuesAgainstBounds(field?.value, bounds) !== "ok"
|
|
98
|
+
: false;
|
|
99
|
+
};
|
|
100
|
+
|
|
73
101
|
return (
|
|
74
102
|
<Table celled className="no_padding">
|
|
75
103
|
<Table.Header>
|
|
@@ -96,9 +124,14 @@ export const DynamicTableField = ({
|
|
|
96
124
|
{rows.map((row, i) => (
|
|
97
125
|
<Table.Row key={i}>
|
|
98
126
|
{row.map((field, k) => (
|
|
99
|
-
<Table.Cell
|
|
127
|
+
<Table.Cell
|
|
128
|
+
key={k}
|
|
129
|
+
width={5}
|
|
130
|
+
error={dateError(field, originalCell(i, field.name))}
|
|
131
|
+
>
|
|
100
132
|
<FieldByWidget
|
|
101
133
|
field={field}
|
|
134
|
+
originalField={originalCell(i, field.name)}
|
|
102
135
|
widget={field.widget}
|
|
103
136
|
onChange={(e, data) => handleChange(i, e, data)}
|
|
104
137
|
scope={scope}
|
|
@@ -147,6 +180,7 @@ export const DynamicTableField = ({
|
|
|
147
180
|
|
|
148
181
|
DynamicTableField.propTypes = {
|
|
149
182
|
field: PropTypes.object,
|
|
183
|
+
originalField: PropTypes.object,
|
|
150
184
|
onChange: PropTypes.func,
|
|
151
185
|
scope: PropTypes.string,
|
|
152
186
|
};
|