@truedat/df 7.12.5 → 7.12.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/DynamicFieldValue.js +1 -1
- package/src/components/DynamicFormViewer.js +4 -3
- package/src/components/DynamicFormWithTranslations.js +3 -3
- package/src/components/EditableDynamicFieldValue.js +1 -1
- package/src/components/FieldViewerValue.js +44 -3
- package/src/components/__tests__/FieldViewerValue.spec.js +10 -6
- package/src/components/__tests__/__snapshots__/FieldViewerValue.spec.js.snap +53 -0
- package/src/components/widgets/DynamicField.js +6 -62
- package/src/components/widgets/DynamicTableField.js +150 -0
- package/src/components/widgets/FieldByWidget.js +63 -0
- package/src/components/widgets/StandardDropdown.js +2 -2
- package/src/components/widgets/StringField.js +2 -1
- package/src/components/widgets/__tests__/DynamicField.spec.js +10 -1
- package/src/components/widgets/__tests__/DynamicTableField.spec.js +257 -0
- package/src/components/widgets/__tests__/__snapshots__/DynamicField.spec.js.snap +97 -0
- package/src/components/widgets/__tests__/__snapshots__/DynamicTableField.spec.js.snap +114 -0
- package/src/templates/components/templateForm/ActiveGroupForm.js +5 -16
- package/src/templates/components/templateForm/FieldDefinition.js +158 -0
- package/src/templates/components/templateForm/FieldForm.js +32 -135
- package/src/templates/components/templateForm/TableValuesForm.js +258 -0
- package/src/templates/components/templateForm/TemplateForm.js +43 -26
- package/src/templates/components/templateForm/ValuesConfiguration.js +67 -0
- package/src/templates/components/templateForm/ValuesField.js +60 -96
- package/src/templates/components/templateForm/ValuesListForm.js +1 -3
- package/src/templates/components/templateForm/__tests__/FieldDefinition.spec.js +227 -0
- package/src/templates/components/templateForm/__tests__/TableValuesForm.spec.js +215 -0
- package/src/templates/components/templateForm/__tests__/ValuesField.spec.js +28 -83
- package/src/templates/components/templateForm/__tests__/__snapshots__/ActiveGroupForm.spec.js.snap +17 -0
- package/src/templates/components/templateForm/__tests__/__snapshots__/FieldDefinition.spec.js.snap +443 -0
- package/src/templates/components/templateForm/__tests__/__snapshots__/FieldForm.spec.js.snap +51 -0
- package/src/templates/components/templateForm/__tests__/__snapshots__/TemplateForm.spec.js.snap +17 -0
- package/src/templates/components/templateForm/__tests__/__snapshots__/ValuesField.spec.js.snap +61 -387
- package/src/templates/components/templateForm/contentValidation.js +22 -3
- package/src/templates/components/templateForm/valueDefinitions.js +16 -2
- package/src/templates/components/templateForm/widgetDefinitions.js +28 -2
- package/src/templates/utils/__tests__/validateContent.spec.js +6 -6
- package/src/templates/utils/applyTemplate.js +72 -23
- package/src/templates/utils/filterValues.js +3 -3
- package/src/templates/utils/parseFieldOptions.js +73 -58
- package/src/templates/utils/parseGroups.js +47 -48
- package/src/templates/utils/validateContent.js +70 -25
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, waitForLoad } from "@truedat/test/render";
|
|
3
|
+
import { screen, fireEvent, within } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
import DynamicTableField from "../DynamicTableField";
|
|
6
|
+
|
|
7
|
+
jest.mock("../FieldByWidget", () => ({
|
|
8
|
+
__esModule: true,
|
|
9
|
+
default: ({ field, onChange }) => {
|
|
10
|
+
const testId = `cell-input-${field.name}`;
|
|
11
|
+
return (
|
|
12
|
+
<input
|
|
13
|
+
data-testid={testId}
|
|
14
|
+
defaultValue={field.value ?? ""}
|
|
15
|
+
onChange={(e) =>
|
|
16
|
+
onChange(e, { name: field.name, value: e.target.value })
|
|
17
|
+
}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
},
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
const baseColumns = [
|
|
24
|
+
{ name: "col1", widget: "text" },
|
|
25
|
+
{ name: "col2", widget: "text" },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
describe("DynamicTableField", () => {
|
|
29
|
+
test("renders header cells (including required asterisk when cardinality is '+' or '1')", async () => {
|
|
30
|
+
const onChange = jest.fn();
|
|
31
|
+
const table_columns = [
|
|
32
|
+
{ name: "col1", widget: "text", cardinality: "+" },
|
|
33
|
+
{ name: "col2", widget: "text" },
|
|
34
|
+
{ name: "col3", widget: "text", cardinality: "1" },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const props = {
|
|
38
|
+
field: {
|
|
39
|
+
name: "tbl",
|
|
40
|
+
value: [
|
|
41
|
+
{
|
|
42
|
+
col1: { value: "a" },
|
|
43
|
+
col2: { value: "b" },
|
|
44
|
+
col3: { value: "c" },
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
values: { table_columns },
|
|
48
|
+
},
|
|
49
|
+
onChange,
|
|
50
|
+
scope: "test",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const rendered = render(<DynamicTableField {...props} />);
|
|
54
|
+
await waitForLoad(rendered);
|
|
55
|
+
expect(rendered.container).toMatchSnapshot();
|
|
56
|
+
|
|
57
|
+
table_columns.forEach((c) => {
|
|
58
|
+
expect(rendered.getByText(c.name)).toBeInTheDocument();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const requiredMarks = rendered.getAllByText("*");
|
|
62
|
+
expect(requiredMarks.length).toBe(2);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("clicking add row calls onChange with a new empty row appended", async () => {
|
|
66
|
+
const user = userEvent.setup({ delay: null });
|
|
67
|
+
const onChange = jest.fn();
|
|
68
|
+
|
|
69
|
+
const initialValue = [
|
|
70
|
+
{ col1: { value: "a" }, col2: { value: "1" } },
|
|
71
|
+
{ col1: { value: "b" }, col2: { value: "2" } },
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
const props = {
|
|
75
|
+
field: {
|
|
76
|
+
name: "tbl",
|
|
77
|
+
value: initialValue,
|
|
78
|
+
values: { table_columns: baseColumns },
|
|
79
|
+
},
|
|
80
|
+
onChange,
|
|
81
|
+
scope: "test",
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const rendered = render(<DynamicTableField {...props} />);
|
|
85
|
+
await waitForLoad(rendered);
|
|
86
|
+
const addButton = rendered.getByTestId("add-button");
|
|
87
|
+
await user.click(addButton);
|
|
88
|
+
|
|
89
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
90
|
+
const [_e, payload] = onChange.mock.calls[0];
|
|
91
|
+
expect(payload.name).toBe("tbl");
|
|
92
|
+
expect(Array.isArray(payload.value)).toBe(true);
|
|
93
|
+
expect(payload.value).toHaveLength(initialValue.length + 1);
|
|
94
|
+
|
|
95
|
+
expect(payload.value[payload.value.length - 1]).toEqual({});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("clicking the trash icon removes the corresponding row and calls onChange", async () => {
|
|
99
|
+
const onChange = jest.fn();
|
|
100
|
+
|
|
101
|
+
const initialValue = [
|
|
102
|
+
{ col1: { value: "a" }, col2: { value: "1" } },
|
|
103
|
+
{ col1: { value: "b" }, col2: { value: "2" } },
|
|
104
|
+
{ col1: { value: "c" }, col2: { value: "3" } },
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
const props = {
|
|
108
|
+
field: {
|
|
109
|
+
name: "tbl",
|
|
110
|
+
value: initialValue,
|
|
111
|
+
values: { table_columns: baseColumns },
|
|
112
|
+
},
|
|
113
|
+
onChange,
|
|
114
|
+
scope: "test",
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const rendered = render(<DynamicTableField {...props} />);
|
|
118
|
+
await waitForLoad(rendered);
|
|
119
|
+
|
|
120
|
+
const firstRow = rendered.container.querySelector("tbody tr");
|
|
121
|
+
expect(firstRow).toBeTruthy();
|
|
122
|
+
|
|
123
|
+
const trashIcon = firstRow.querySelector(".selectable");
|
|
124
|
+
expect(trashIcon).toBeTruthy();
|
|
125
|
+
|
|
126
|
+
fireEvent.click(trashIcon);
|
|
127
|
+
|
|
128
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
129
|
+
const [, payload] = onChange.mock.calls[0];
|
|
130
|
+
|
|
131
|
+
expect(payload.name).toBe("tbl");
|
|
132
|
+
expect(payload.value).toHaveLength(2);
|
|
133
|
+
|
|
134
|
+
expect(payload.value[0]).toEqual(initialValue[1]);
|
|
135
|
+
expect(payload.value[1]).toEqual(initialValue[2]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("editing a cell calls onChange with updated structure { origin: 'user', value }", async () => {
|
|
139
|
+
const user = userEvent.setup({ delay: null });
|
|
140
|
+
const onChange = jest.fn();
|
|
141
|
+
|
|
142
|
+
const initialValue = [
|
|
143
|
+
{ col1: { value: "old-a" }, col2: { value: "1" } },
|
|
144
|
+
{ col1: { value: "old-b" }, col2: { value: "2" } },
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const props = {
|
|
148
|
+
field: {
|
|
149
|
+
name: "tbl",
|
|
150
|
+
value: initialValue,
|
|
151
|
+
values: { table_columns: baseColumns },
|
|
152
|
+
},
|
|
153
|
+
onChange,
|
|
154
|
+
scope: "test",
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const rendered = render(<DynamicTableField {...props} />);
|
|
158
|
+
await waitForLoad(rendered);
|
|
159
|
+
|
|
160
|
+
const input = rendered.getAllByTestId("cell-input-col1")[0];
|
|
161
|
+
await user.clear(input);
|
|
162
|
+
await user.type(input, "new-a");
|
|
163
|
+
|
|
164
|
+
// Last call should have the updated row 0 col1 shape
|
|
165
|
+
expect(onChange).toHaveBeenCalled();
|
|
166
|
+
const [, payload] = onChange.mock.calls[onChange.mock.calls.length - 1];
|
|
167
|
+
|
|
168
|
+
expect(payload.name).toBe("tbl");
|
|
169
|
+
expect(payload.value).toHaveLength(2);
|
|
170
|
+
|
|
171
|
+
expect(payload.value[0].col1).toEqual({
|
|
172
|
+
origin: "user",
|
|
173
|
+
value: "new-a",
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect(payload.value[0].col2).toEqual({ value: "1" });
|
|
177
|
+
expect(payload.value[1]).toEqual(initialValue[1]);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("removeDeletedColumns strips keys not present in table_columns before emitting onChange", async () => {
|
|
181
|
+
const user = userEvent.setup({ delay: null });
|
|
182
|
+
const onChange = jest.fn();
|
|
183
|
+
|
|
184
|
+
const columns = [
|
|
185
|
+
{ name: "col1", widget: "text" },
|
|
186
|
+
{ name: "col2", widget: "text" },
|
|
187
|
+
// Note: no 'ghost' column
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
const initialValue = [
|
|
191
|
+
// Row contains an extra 'ghost' key that should be stripped on change
|
|
192
|
+
{ col1: { value: "x" }, col2: { value: "y" }, ghost: { value: "zzz" } },
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
const props = {
|
|
196
|
+
field: {
|
|
197
|
+
name: "tbl",
|
|
198
|
+
value: initialValue,
|
|
199
|
+
values: { table_columns: columns },
|
|
200
|
+
},
|
|
201
|
+
onChange,
|
|
202
|
+
scope: "test",
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const rendered = render(<DynamicTableField {...props} />);
|
|
206
|
+
await waitForLoad(rendered);
|
|
207
|
+
|
|
208
|
+
// Trigger a change on col2; this will call handleChange → removeDeletedColumns
|
|
209
|
+
const input = rendered.getByTestId("cell-input-col2");
|
|
210
|
+
await user.clear(input);
|
|
211
|
+
await user.type(input, "updated");
|
|
212
|
+
|
|
213
|
+
expect(onChange).toHaveBeenCalled();
|
|
214
|
+
const [, payload] = onChange.mock.calls[onChange.mock.calls.length - 1];
|
|
215
|
+
|
|
216
|
+
// 'ghost' should be omitted from the emitted row
|
|
217
|
+
expect(payload.value[0]).toEqual({
|
|
218
|
+
col1: { value: "x" },
|
|
219
|
+
col2: { origin: "user", value: "updated" },
|
|
220
|
+
});
|
|
221
|
+
expect(payload.value[0].ghost).toBeUndefined();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("delete button exists per row and add button exists in footer", async () => {
|
|
225
|
+
const onChange = jest.fn();
|
|
226
|
+
|
|
227
|
+
const initialValue = [
|
|
228
|
+
{ col1: { value: "a" }, col2: { value: "1" } },
|
|
229
|
+
{ col1: { value: "b" }, col2: { value: "2" } },
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
const props = {
|
|
233
|
+
field: {
|
|
234
|
+
name: "tbl",
|
|
235
|
+
value: initialValue,
|
|
236
|
+
values: { table_columns: baseColumns },
|
|
237
|
+
},
|
|
238
|
+
onChange,
|
|
239
|
+
scope: "test",
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const rendered = render(<DynamicTableField {...props} />);
|
|
243
|
+
await waitForLoad(rendered);
|
|
244
|
+
|
|
245
|
+
// Footer add button
|
|
246
|
+
expect(rendered.getByTestId("add-button")).toBeInTheDocument();
|
|
247
|
+
|
|
248
|
+
// Each row should render a Button containing the trash Icon
|
|
249
|
+
const rows = rendered.container.querySelectorAll("tbody tr");
|
|
250
|
+
expect(rows.length).toBe(2);
|
|
251
|
+
|
|
252
|
+
rows.forEach((row) => {
|
|
253
|
+
const trashIcon = row.querySelector(".selectable");
|
|
254
|
+
expect(trashIcon).toBeTruthy();
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
});
|
|
@@ -391,6 +391,103 @@ exports[`<DynamicField /> matches the latest snapshot (dropdown widget) 1`] = `
|
|
|
391
391
|
</div>
|
|
392
392
|
`;
|
|
393
393
|
|
|
394
|
+
exports[`<DynamicField /> matches the latest snapshot (dynamic_table widget) 1`] = `
|
|
395
|
+
<div>
|
|
396
|
+
<div
|
|
397
|
+
class="field"
|
|
398
|
+
data-testid="form-field"
|
|
399
|
+
>
|
|
400
|
+
<label />
|
|
401
|
+
<table
|
|
402
|
+
class="ui celled table no_padding"
|
|
403
|
+
>
|
|
404
|
+
<thead
|
|
405
|
+
class=""
|
|
406
|
+
>
|
|
407
|
+
<tr
|
|
408
|
+
class=""
|
|
409
|
+
>
|
|
410
|
+
<th
|
|
411
|
+
class=""
|
|
412
|
+
>
|
|
413
|
+
col
|
|
414
|
+
<span
|
|
415
|
+
class="is_required"
|
|
416
|
+
>
|
|
417
|
+
*
|
|
418
|
+
</span>
|
|
419
|
+
</th>
|
|
420
|
+
<th
|
|
421
|
+
class=""
|
|
422
|
+
/>
|
|
423
|
+
</tr>
|
|
424
|
+
</thead>
|
|
425
|
+
<tbody
|
|
426
|
+
class=""
|
|
427
|
+
>
|
|
428
|
+
<tr
|
|
429
|
+
class=""
|
|
430
|
+
>
|
|
431
|
+
<td
|
|
432
|
+
class="five wide"
|
|
433
|
+
>
|
|
434
|
+
<div
|
|
435
|
+
class="field"
|
|
436
|
+
>
|
|
437
|
+
<div
|
|
438
|
+
class="ui input"
|
|
439
|
+
>
|
|
440
|
+
<input
|
|
441
|
+
name="col"
|
|
442
|
+
type="text"
|
|
443
|
+
value="foo"
|
|
444
|
+
/>
|
|
445
|
+
</div>
|
|
446
|
+
</div>
|
|
447
|
+
</td>
|
|
448
|
+
<td
|
|
449
|
+
class="one wide"
|
|
450
|
+
style="text-align: center; padding: 1px;"
|
|
451
|
+
>
|
|
452
|
+
<button
|
|
453
|
+
class="ui red mini basic icon button"
|
|
454
|
+
>
|
|
455
|
+
<i
|
|
456
|
+
aria-hidden="true"
|
|
457
|
+
class="red trash alternate outline icon selectable"
|
|
458
|
+
/>
|
|
459
|
+
</button>
|
|
460
|
+
</td>
|
|
461
|
+
</tr>
|
|
462
|
+
</tbody>
|
|
463
|
+
<tfoot
|
|
464
|
+
class=""
|
|
465
|
+
>
|
|
466
|
+
<tr
|
|
467
|
+
class=""
|
|
468
|
+
>
|
|
469
|
+
<th
|
|
470
|
+
class=""
|
|
471
|
+
colspan="2"
|
|
472
|
+
>
|
|
473
|
+
<button
|
|
474
|
+
class="ui small compact icon right floated button"
|
|
475
|
+
data-testid="add-button"
|
|
476
|
+
name="add_button"
|
|
477
|
+
>
|
|
478
|
+
<i
|
|
479
|
+
aria-hidden="true"
|
|
480
|
+
class="green add icon"
|
|
481
|
+
/>
|
|
482
|
+
</button>
|
|
483
|
+
</th>
|
|
484
|
+
</tr>
|
|
485
|
+
</tfoot>
|
|
486
|
+
</table>
|
|
487
|
+
</div>
|
|
488
|
+
</div>
|
|
489
|
+
`;
|
|
490
|
+
|
|
394
491
|
exports[`<DynamicField /> matches the latest snapshot (image widget) 1`] = `
|
|
395
492
|
<div>
|
|
396
493
|
<div
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`DynamicTableField renders header cells (including required asterisk when cardinality is '+' or '1') 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<table
|
|
6
|
+
class="ui celled table no_padding"
|
|
7
|
+
>
|
|
8
|
+
<thead
|
|
9
|
+
class=""
|
|
10
|
+
>
|
|
11
|
+
<tr
|
|
12
|
+
class=""
|
|
13
|
+
>
|
|
14
|
+
<th
|
|
15
|
+
class=""
|
|
16
|
+
>
|
|
17
|
+
col1
|
|
18
|
+
<span
|
|
19
|
+
class="is_required"
|
|
20
|
+
>
|
|
21
|
+
*
|
|
22
|
+
</span>
|
|
23
|
+
</th>
|
|
24
|
+
<th
|
|
25
|
+
class=""
|
|
26
|
+
>
|
|
27
|
+
col2
|
|
28
|
+
</th>
|
|
29
|
+
<th
|
|
30
|
+
class=""
|
|
31
|
+
>
|
|
32
|
+
col3
|
|
33
|
+
<span
|
|
34
|
+
class="is_required"
|
|
35
|
+
>
|
|
36
|
+
*
|
|
37
|
+
</span>
|
|
38
|
+
</th>
|
|
39
|
+
<th
|
|
40
|
+
class=""
|
|
41
|
+
/>
|
|
42
|
+
</tr>
|
|
43
|
+
</thead>
|
|
44
|
+
<tbody
|
|
45
|
+
class=""
|
|
46
|
+
>
|
|
47
|
+
<tr
|
|
48
|
+
class=""
|
|
49
|
+
>
|
|
50
|
+
<td
|
|
51
|
+
class="five wide"
|
|
52
|
+
>
|
|
53
|
+
<input
|
|
54
|
+
data-testid="cell-input-col1"
|
|
55
|
+
value="a"
|
|
56
|
+
/>
|
|
57
|
+
</td>
|
|
58
|
+
<td
|
|
59
|
+
class="five wide"
|
|
60
|
+
>
|
|
61
|
+
<input
|
|
62
|
+
data-testid="cell-input-col2"
|
|
63
|
+
value="b"
|
|
64
|
+
/>
|
|
65
|
+
</td>
|
|
66
|
+
<td
|
|
67
|
+
class="five wide"
|
|
68
|
+
>
|
|
69
|
+
<input
|
|
70
|
+
data-testid="cell-input-col3"
|
|
71
|
+
value="c"
|
|
72
|
+
/>
|
|
73
|
+
</td>
|
|
74
|
+
<td
|
|
75
|
+
class="one wide"
|
|
76
|
+
style="text-align: center; padding: 1px;"
|
|
77
|
+
>
|
|
78
|
+
<button
|
|
79
|
+
class="ui red mini basic icon button"
|
|
80
|
+
>
|
|
81
|
+
<i
|
|
82
|
+
aria-hidden="true"
|
|
83
|
+
class="red trash alternate outline icon selectable"
|
|
84
|
+
/>
|
|
85
|
+
</button>
|
|
86
|
+
</td>
|
|
87
|
+
</tr>
|
|
88
|
+
</tbody>
|
|
89
|
+
<tfoot
|
|
90
|
+
class=""
|
|
91
|
+
>
|
|
92
|
+
<tr
|
|
93
|
+
class=""
|
|
94
|
+
>
|
|
95
|
+
<th
|
|
96
|
+
class=""
|
|
97
|
+
colspan="4"
|
|
98
|
+
>
|
|
99
|
+
<button
|
|
100
|
+
class="ui small compact icon right floated button"
|
|
101
|
+
data-testid="add-button"
|
|
102
|
+
name="add_button"
|
|
103
|
+
>
|
|
104
|
+
<i
|
|
105
|
+
aria-hidden="true"
|
|
106
|
+
class="green add icon"
|
|
107
|
+
/>
|
|
108
|
+
</button>
|
|
109
|
+
</th>
|
|
110
|
+
</tr>
|
|
111
|
+
</tfoot>
|
|
112
|
+
</table>
|
|
113
|
+
</div>
|
|
114
|
+
`;
|
|
@@ -10,9 +10,10 @@ import {
|
|
|
10
10
|
Input,
|
|
11
11
|
Icon,
|
|
12
12
|
} from "semantic-ui-react";
|
|
13
|
-
import {
|
|
13
|
+
import { useIntl } from "react-intl";
|
|
14
14
|
import { dropAt, swap } from "@truedat/core/services/arrays";
|
|
15
15
|
import FieldForm from "./FieldForm";
|
|
16
|
+
import { defaultFieldDefinition } from "./widgetDefinitions";
|
|
16
17
|
|
|
17
18
|
export const ActiveGroupForm = ({
|
|
18
19
|
activeGroup,
|
|
@@ -52,20 +53,8 @@ export const ActiveGroupForm = ({
|
|
|
52
53
|
};
|
|
53
54
|
|
|
54
55
|
const handleAddField = () => {
|
|
55
|
-
const newField = {
|
|
56
|
-
name: "new_field",
|
|
57
|
-
label: "New Field",
|
|
58
|
-
widget: "string",
|
|
59
|
-
cardinality: "?",
|
|
60
|
-
type: "string",
|
|
61
|
-
default: {
|
|
62
|
-
value: "",
|
|
63
|
-
origin: "default",
|
|
64
|
-
},
|
|
65
|
-
values: null,
|
|
66
|
-
};
|
|
67
56
|
const name = `${groupNamePrefix}.fields`;
|
|
68
|
-
onChange(null, { name, value: [...fields,
|
|
57
|
+
onChange(null, { name, value: [...fields, defaultFieldDefinition] });
|
|
69
58
|
setActiveField(fields.length);
|
|
70
59
|
};
|
|
71
60
|
|
|
@@ -166,8 +155,8 @@ export const ActiveGroupForm = ({
|
|
|
166
155
|
{_.isEmpty(_.prop(field.name)(dependencies))
|
|
167
156
|
? null
|
|
168
157
|
: _.map(({ label, name }) => (
|
|
169
|
-
|
|
170
|
-
|
|
158
|
+
<Label key={name}>{label}</Label>
|
|
159
|
+
))(_.prop(field.name)(dependencies))}
|
|
171
160
|
</>
|
|
172
161
|
),
|
|
173
162
|
},
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import _ from "lodash/fp";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import { Form } from "semantic-ui-react";
|
|
4
|
+
import { useIntl } from "react-intl";
|
|
5
|
+
import { useEffect } from "react";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
getCardinalityOptions,
|
|
9
|
+
getWidgetOptions,
|
|
10
|
+
getTypeOptions,
|
|
11
|
+
WIDGETS,
|
|
12
|
+
} from "./widgetDefinitions";
|
|
13
|
+
import { getValues } from "./valueDefinitions";
|
|
14
|
+
|
|
15
|
+
const defaultType = (values) => _.flow(_.map("value"), _.first)(values);
|
|
16
|
+
|
|
17
|
+
const FieldDefinition = ({
|
|
18
|
+
allowedTypes = [],
|
|
19
|
+
allowedWidgets = [],
|
|
20
|
+
cardinality,
|
|
21
|
+
defaultField,
|
|
22
|
+
fieldNamePrefix,
|
|
23
|
+
mandatory,
|
|
24
|
+
onChange,
|
|
25
|
+
subscribableField,
|
|
26
|
+
type,
|
|
27
|
+
valueName,
|
|
28
|
+
widget,
|
|
29
|
+
}) => {
|
|
30
|
+
const { formatMessage } = useIntl();
|
|
31
|
+
|
|
32
|
+
const changeValue = (name, value) =>
|
|
33
|
+
value == "null"
|
|
34
|
+
? onChange(null, { name, value: null })
|
|
35
|
+
: onChange(null, {
|
|
36
|
+
name,
|
|
37
|
+
value: { [value]: null },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const handleTypeChange = (e, data) => {
|
|
41
|
+
const { value } = data;
|
|
42
|
+
changeValue(valueName, defaultType(getValues(widget, value)) || "null");
|
|
43
|
+
onChange(e, {
|
|
44
|
+
name: defaultField,
|
|
45
|
+
value: { value: "", origin: "default" },
|
|
46
|
+
});
|
|
47
|
+
onChange(e, { name: subscribableField, value: false });
|
|
48
|
+
onChange(e, data);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleCardinalityChange = (e, data) => {
|
|
52
|
+
const value = data?.value;
|
|
53
|
+
onChange(e, data);
|
|
54
|
+
if (_.includes(value)(["1", "+"]) && !_.isEmpty(mandatory))
|
|
55
|
+
onChange(e, { name: `${fieldNamePrefix}.mandatory`, value: null });
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleWidgetChange = (e, data) => {
|
|
59
|
+
const { value } = data;
|
|
60
|
+
const types = _.find({ value })(WIDGETS).types;
|
|
61
|
+
const updatedType = _.indexOf(type)(types) < 0 ? _.head(types) : type;
|
|
62
|
+
changeValue(
|
|
63
|
+
valueName,
|
|
64
|
+
defaultType(getValues(value, updatedType)) || "null",
|
|
65
|
+
);
|
|
66
|
+
onChange(e, {
|
|
67
|
+
name: defaultField,
|
|
68
|
+
value: { value: "", origin: "default" },
|
|
69
|
+
});
|
|
70
|
+
onChange(e, { name: subscribableField, value: false });
|
|
71
|
+
onChange(e, data);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const typeCheckOnChange = (widgetOption) => {
|
|
75
|
+
if (widgetOption && _.indexOf(type)(widgetOption.types) < 0)
|
|
76
|
+
onChange(null, {
|
|
77
|
+
name: `${fieldNamePrefix}.type`,
|
|
78
|
+
value: widgetOption.types[0],
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const cardinalityCheckOnChange = (widgetOption) => {
|
|
83
|
+
if (
|
|
84
|
+
widgetOption &&
|
|
85
|
+
_.indexOf(cardinality)(widgetOption.cardinalities) < 0
|
|
86
|
+
)
|
|
87
|
+
onChange(null, {
|
|
88
|
+
name: `${fieldNamePrefix}.cardinality`,
|
|
89
|
+
value: widgetOption.cardinalities[0],
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
//On widget change, verify if type and cardinality are still valid
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
const widgetOption = _.find({ value: widget })(WIDGETS);
|
|
96
|
+
typeCheckOnChange(widgetOption);
|
|
97
|
+
cardinalityCheckOnChange(widgetOption);
|
|
98
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
99
|
+
}, [widget, type]);
|
|
100
|
+
|
|
101
|
+
const widgetOptions = _.isEmpty(allowedWidgets)
|
|
102
|
+
? getWidgetOptions()
|
|
103
|
+
: _.filter((widget) => allowedWidgets.includes(widget.value))(
|
|
104
|
+
getWidgetOptions(),
|
|
105
|
+
);
|
|
106
|
+
const typeOptions = _.isEmpty(allowedTypes)
|
|
107
|
+
? getTypeOptions(widget)
|
|
108
|
+
: _.filter((type) => allowedTypes.includes(type.value))(
|
|
109
|
+
getTypeOptions(widget),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<Form.Group size="small" widths="equal">
|
|
114
|
+
<Form.Dropdown
|
|
115
|
+
name={`${fieldNamePrefix}.widget`}
|
|
116
|
+
fluid
|
|
117
|
+
selection
|
|
118
|
+
label={formatMessage({ id: "template.field.widget" })}
|
|
119
|
+
value={widget}
|
|
120
|
+
options={widgetOptions}
|
|
121
|
+
required
|
|
122
|
+
onChange={handleWidgetChange}
|
|
123
|
+
/>
|
|
124
|
+
<Form.Dropdown
|
|
125
|
+
fluid
|
|
126
|
+
selection
|
|
127
|
+
label={formatMessage({ id: "template.field.type" })}
|
|
128
|
+
onChange={handleTypeChange}
|
|
129
|
+
name={`${fieldNamePrefix}.type`}
|
|
130
|
+
value={type}
|
|
131
|
+
required
|
|
132
|
+
options={typeOptions}
|
|
133
|
+
/>
|
|
134
|
+
<Form.Dropdown
|
|
135
|
+
fluid
|
|
136
|
+
selection
|
|
137
|
+
label={formatMessage({ id: "template.field.cardinality" })}
|
|
138
|
+
name={`${fieldNamePrefix}.cardinality`}
|
|
139
|
+
value={cardinality}
|
|
140
|
+
onChange={handleCardinalityChange}
|
|
141
|
+
required
|
|
142
|
+
options={getCardinalityOptions(widget)}
|
|
143
|
+
/>
|
|
144
|
+
</Form.Group>
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
FieldDefinition.propTypes = {
|
|
149
|
+
fieldNamePrefix: PropTypes.string,
|
|
150
|
+
handleWidgetChange: PropTypes.func,
|
|
151
|
+
handleTypeChange: PropTypes.func,
|
|
152
|
+
handleCardinalityChange: PropTypes.func,
|
|
153
|
+
type: PropTypes.string,
|
|
154
|
+
widget: PropTypes.string,
|
|
155
|
+
cardinality: PropTypes.string,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export default FieldDefinition;
|