@truedat/df 8.13.4 → 8.13.6

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 (39) hide show
  1. package/package.json +4 -4
  2. package/src/components/DynamicForm.js +3 -0
  3. package/src/components/DynamicFormWithTranslations.js +3 -0
  4. package/src/components/FieldGroupSegment.js +3 -0
  5. package/src/components/FieldGroupWithTranslations.js +13 -0
  6. package/src/components/SelectDynamicFormWithTranslations.js +7 -1
  7. package/src/components/SelectableDynamicForm.js +7 -1
  8. package/src/components/__tests__/__snapshots__/SelectableDynamicForm.spec.js.snap +18 -1
  9. package/src/components/__tests__/originalContentFlow.spec.js +131 -0
  10. package/src/components/widgets/DateField.js +18 -9
  11. package/src/components/widgets/DateTimeField.js +26 -9
  12. package/src/components/widgets/DynamicField.js +54 -1
  13. package/src/components/widgets/DynamicTableField.js +41 -7
  14. package/src/components/widgets/FieldByWidget.js +36 -6
  15. package/src/components/widgets/__tests__/dateBoundsByWidget.spec.js +133 -0
  16. package/src/components/widgets/__tests__/dateBoundsEndToEnd.spec.js +83 -0
  17. package/src/components/widgets/__tests__/datePickerBoundaries.spec.js +124 -0
  18. package/src/components/widgets/__tests__/dateValueValidation.spec.js +215 -0
  19. package/src/components/widgets/__tests__/existingDateRestrictions.spec.js +354 -0
  20. package/src/templates/components/templateForm/DateRestrictionsForm.js +217 -0
  21. package/src/templates/components/templateForm/DefaultValue.js +79 -11
  22. package/src/templates/components/templateForm/FieldDefinition.js +15 -1
  23. package/src/templates/components/templateForm/TemplateForm.js +5 -0
  24. package/src/templates/components/templateForm/ValuesField.js +7 -0
  25. package/src/templates/components/templateForm/__tests__/FieldDefinition.spec.js +48 -0
  26. package/src/templates/components/templateForm/__tests__/TemplateForm.spec.js +107 -0
  27. package/src/templates/components/templateForm/__tests__/dateRestrictionsForm.spec.js +429 -0
  28. package/src/templates/components/templateForm/__tests__/dateRestrictionsValidation.spec.js +166 -0
  29. package/src/templates/components/templateForm/__tests__/defaultValueBounds.spec.js +191 -0
  30. package/src/templates/components/templateForm/__tests__/defaultValueRestrictions.spec.js +175 -0
  31. package/src/templates/components/templateForm/contentValidation.js +184 -0
  32. package/src/templates/utils/__tests__/dateRestrictions.spec.js +578 -0
  33. package/src/templates/utils/__tests__/originalTableRows.spec.js +129 -0
  34. package/src/templates/utils/__tests__/validateContentDateRestrictions.spec.js +519 -0
  35. package/src/templates/utils/applyTemplate.js +27 -20
  36. package/src/templates/utils/dateRestrictions.js +497 -0
  37. package/src/templates/utils/dateValueUnchanged.js +12 -0
  38. package/src/templates/utils/originalTableRows.js +41 -0
  39. package/src/templates/utils/validateContent.js +75 -4
@@ -0,0 +1,354 @@
1
+ import { render, waitForLoad } from "@truedat/test/render";
2
+ import { fireEvent } from "@testing-library/react";
3
+ import { DynamicField } from "../DynamicField";
4
+ import DynamicTableField from "../DynamicTableField";
5
+ import { applyTemplate } from "../../../templates/utils/applyTemplate";
6
+ import { validateContent } from "../../../templates/utils/validateContent";
7
+
8
+ const restrictions = {
9
+ between: { mode: "static", start: "2026-01-01", end: "2026-12-31" },
10
+ resolved: { min: "2026-01-01", max: "2026-12-31" },
11
+ };
12
+
13
+ const dateField = (type, value) => ({
14
+ name: "date",
15
+ label: "Date",
16
+ type,
17
+ widget: type,
18
+ restrictions,
19
+ value: { value, origin: "user" },
20
+ });
21
+
22
+ describe("DynamicField existing date restrictions", () => {
23
+ it("ignores origin-only differences in wrapped multiple date values", () => {
24
+ const { container } = render(
25
+ <DynamicField
26
+ field={dateField("date", [{ value: "2025-01-01", origin: "user" }])}
27
+ originalField={{ value: [{ value: "2025-01-01", origin: "default" }] }}
28
+ onChange={jest.fn()}
29
+ />,
30
+ );
31
+
32
+ expect(container.querySelector(".error")).toBeNull();
33
+ expect(container.querySelector("input")).not.toHaveAttribute("min");
34
+ });
35
+ it.each([
36
+ ["date", "2025-01-01"],
37
+ ["datetime", "2025-01-01T12:00"],
38
+ ])("does not mark an unchanged existing %s as an error", (type, value) => {
39
+ const { container } = render(
40
+ <DynamicField
41
+ field={dateField(type, value)}
42
+ originalField={{ value, origin: "default" }}
43
+ onChange={jest.fn()}
44
+ />,
45
+ );
46
+
47
+ expect(container.querySelector(".error")).toBeNull();
48
+ });
49
+
50
+ it.each([
51
+ ["date", "2025-01-01"],
52
+ ["datetime", "2025-01-01T12:00"],
53
+ ])("removes native bounds for an unchanged existing %s", (type, value) => {
54
+ const { container } = render(
55
+ <DynamicField
56
+ field={dateField(type, value)}
57
+ originalField={{ value, origin: "default" }}
58
+ onChange={jest.fn()}
59
+ />,
60
+ );
61
+
62
+ expect(container.querySelector("input")).not.toHaveAttribute("min");
63
+ expect(container.querySelector("input")).not.toHaveAttribute("max");
64
+ });
65
+
66
+ it.each([undefined, null, { value: "2024-01-01", origin: "user" }])(
67
+ "marks a new or changed out-of-range value as an error (baseline %j)",
68
+ (originalField) => {
69
+ const { getByTestId } = render(
70
+ <DynamicField
71
+ field={dateField("date", "2025-01-01")}
72
+ originalField={originalField}
73
+ onChange={jest.fn()}
74
+ />,
75
+ );
76
+
77
+ expect(getByTestId("form-field")).toHaveClass("error");
78
+ },
79
+ );
80
+
81
+ it.each([undefined, { value: "2024-01-01", origin: "user" }])(
82
+ "retains native bounds for a new or changed value (baseline %j)",
83
+ (originalField) => {
84
+ const { container } = render(
85
+ <DynamicField
86
+ field={dateField("date", "2025-01-01")}
87
+ originalField={originalField}
88
+ onChange={jest.fn()}
89
+ />,
90
+ );
91
+
92
+ expect(container.querySelector("input")).toHaveAttribute(
93
+ "min",
94
+ "2026-01-01",
95
+ );
96
+ expect(container.querySelector("input")).toHaveAttribute(
97
+ "max",
98
+ "2026-12-31",
99
+ );
100
+ },
101
+ );
102
+
103
+ it("restores bounds when the existing date changes", () => {
104
+ const originalField = { value: "2025-01-01", origin: "user" };
105
+ const { container, rerender } = render(
106
+ <DynamicField
107
+ field={dateField("date", "2025-01-01")}
108
+ originalField={originalField}
109
+ onChange={jest.fn()}
110
+ />,
111
+ );
112
+
113
+ rerender(
114
+ <DynamicField
115
+ field={dateField("date", "2025-02-01")}
116
+ originalField={originalField}
117
+ onChange={jest.fn()}
118
+ />,
119
+ );
120
+
121
+ expect(container.querySelector("input")).toHaveAttribute(
122
+ "min",
123
+ "2026-01-01",
124
+ );
125
+ });
126
+
127
+ it("does not suppress required errors on an unchanged empty field", () => {
128
+ const { getByText } = render(
129
+ <DynamicField
130
+ field={{ ...dateField("date", null), required: true }}
131
+ originalField={{ value: null, origin: "user" }}
132
+ onChange={jest.fn()}
133
+ />,
134
+ );
135
+
136
+ expect(
137
+ getByText("template.form.validation.empty_required"),
138
+ ).toBeInTheDocument();
139
+ });
140
+ });
141
+
142
+ const tableField = (rows, columns = [dateField("date")]) => ({
143
+ name: "table",
144
+ type: "table",
145
+ widget: "dynamic_table",
146
+ value: rows,
147
+ values: { table_columns: columns },
148
+ });
149
+
150
+ describe("DynamicTableField existing date restrictions", () => {
151
+ const columns = [
152
+ dateField("date"),
153
+ { name: "sibling", type: "string", widget: "string" },
154
+ ];
155
+ const schema = {
156
+ content: [
157
+ {
158
+ name: "group",
159
+ fields: [{ ...tableField([], columns), type: "dynamic_table" }],
160
+ },
161
+ ],
162
+ };
163
+ const row = (value) => ({
164
+ date: { value, origin: "user" },
165
+ sibling: { value: "old", origin: "user" },
166
+ });
167
+
168
+ const mountTable = async (originalRows) => {
169
+ const onChange = jest.fn();
170
+ const originalField = { value: originalRows, origin: "user" };
171
+ const view = (rows) => (
172
+ <DynamicTableField
173
+ field={tableField(rows, columns)}
174
+ originalField={originalField}
175
+ onChange={onChange}
176
+ />
177
+ );
178
+ const initial = applyTemplate(schema)({ table: originalField }).table.value;
179
+ const rendered = render(view(initial));
180
+ await waitForLoad(rendered);
181
+ const update = () => {
182
+ const rows = onChange.mock.calls.at(-1)[1].value;
183
+ const normalized = applyTemplate(schema)({
184
+ table: { value: rows, origin: "user" },
185
+ });
186
+ rendered.rerender(view(normalized.table?.value || []));
187
+ return normalized;
188
+ };
189
+ return { ...rendered, update, originalField };
190
+ };
191
+
192
+ it("keeps the survivor exempt after removing the first row and editing a sibling", async () => {
193
+ const rendered = await mountTable([row("2025-01-01"), row("2024-01-01")]);
194
+ fireEvent.click(rendered.container.querySelector("tbody .selectable"));
195
+ rendered.update();
196
+ fireEvent.change(
197
+ rendered.container.querySelector('input[name="sibling"]'),
198
+ { target: { value: "edited" } },
199
+ );
200
+ const normalized = rendered.update();
201
+
202
+ expect(rendered.container.querySelector("td.error")).toBeNull();
203
+ expect(
204
+ rendered.container.querySelector('input[type="date"]'),
205
+ ).not.toHaveAttribute("min");
206
+ expect(
207
+ validateContent(schema, { table: rendered.originalField })(normalized),
208
+ ).toEqual([]);
209
+ });
210
+
211
+ it.each([false, true])(
212
+ "rejects an added row matching a legacy date (delete first: %s)",
213
+ async (removeFirst) => {
214
+ const rendered = await mountTable([row("2025-01-01")]);
215
+ if (removeFirst) {
216
+ fireEvent.click(rendered.container.querySelector("tbody .selectable"));
217
+ rendered.update();
218
+ }
219
+ fireEvent.click(rendered.getByTestId("add-button"));
220
+ rendered.update();
221
+ const inputs = rendered.container.querySelectorAll('input[type="date"]');
222
+ fireEvent.change(inputs[inputs.length - 1], {
223
+ target: { value: "2025-01-01" },
224
+ });
225
+ const normalized = rendered.update();
226
+
227
+ expect(rendered.container.querySelector("td.error")).not.toBeNull();
228
+ expect(
229
+ validateContent(schema, { table: rendered.originalField })(normalized),
230
+ ).toEqual([
231
+ {
232
+ error: "date out of range",
233
+ field: `table.${removeFirst ? 0 : 1}.date`,
234
+ values: restrictions.resolved,
235
+ },
236
+ ]);
237
+ },
238
+ );
239
+
240
+ it("does not mark unchanged existing cells as errors", async () => {
241
+ const value = [{ date: { value: "2025-01-01", origin: "user" } }];
242
+ const rendered = render(
243
+ <DynamicTableField
244
+ field={tableField(value)}
245
+ originalField={{ value, origin: "user" }}
246
+ onChange={jest.fn()}
247
+ />,
248
+ );
249
+ await waitForLoad(rendered);
250
+
251
+ expect(rendered.container.querySelector(".error")).toBeNull();
252
+ });
253
+
254
+ it("bounds changed cells independently of unchanged neighboring cells", async () => {
255
+ const originalRows = [
256
+ { date: { value: "2025-01-01", origin: "user" } },
257
+ { date: { value: "2025-02-01", origin: "user" } },
258
+ ];
259
+ const rendered = render(
260
+ <DynamicTableField
261
+ field={tableField([
262
+ originalRows[0],
263
+ { date: { value: "2025-03-01", origin: "user" } },
264
+ { date: { value: "2025-04-01", origin: "user" } },
265
+ ])}
266
+ originalField={{ value: originalRows, origin: "user" }}
267
+ onChange={jest.fn()}
268
+ />,
269
+ );
270
+ await waitForLoad(rendered);
271
+ const inputs = rendered.container.querySelectorAll("input");
272
+
273
+ expect(inputs[0]).not.toHaveAttribute("min");
274
+ expect(inputs[1]).toHaveAttribute("min", "2026-01-01");
275
+ expect(inputs[2]).toHaveAttribute("min", "2026-01-01");
276
+ });
277
+
278
+ it("retains cell errors when a table has no original content", async () => {
279
+ const rendered = render(
280
+ <DynamicTableField
281
+ field={tableField([{ date: { value: "2025-01-01", origin: "user" } }])}
282
+ onChange={jest.fn()}
283
+ />,
284
+ );
285
+ await waitForLoad(rendered);
286
+
287
+ expect(rendered.container.querySelector("td.error")).not.toBeNull();
288
+ });
289
+
290
+ it("passes original cell baselines recursively into nested tables", async () => {
291
+ const nestedRows = [{ date: { value: "2025-01-01", origin: "user" } }];
292
+ const rows = [{ table: { value: nestedRows, origin: "user" } }];
293
+ const rendered = render(
294
+ <DynamicTableField
295
+ field={tableField(rows, [tableField([])])}
296
+ originalField={{ value: rows, origin: "user" }}
297
+ onChange={jest.fn()}
298
+ />,
299
+ );
300
+ await waitForLoad(rendered);
301
+
302
+ expect(rendered.container.querySelector("input")).not.toHaveAttribute(
303
+ "min",
304
+ );
305
+ });
306
+
307
+ it("preserves nested survivor baselines when a nested first row is removed", async () => {
308
+ const originalRows = [
309
+ {
310
+ table: {
311
+ value: [row("2025-01-01"), row("2024-01-01")],
312
+ origin: "user",
313
+ },
314
+ },
315
+ ];
316
+ const onChange = jest.fn();
317
+ const nestedColumn = { ...tableField([], columns), type: "dynamic_table" };
318
+ const view = (rows) => (
319
+ <DynamicTableField
320
+ field={tableField(rows, [nestedColumn])}
321
+ originalField={{ value: originalRows }}
322
+ onChange={onChange}
323
+ />
324
+ );
325
+ const rendered = render(view(originalRows));
326
+ await waitForLoad(rendered);
327
+ const nestedTable = rendered.container
328
+ .querySelector('input[type="date"]')
329
+ .closest("table");
330
+ fireEvent.click(nestedTable.querySelector("tbody .selectable"));
331
+ rendered.rerender(view(onChange.mock.calls.at(-1)[1].value));
332
+
333
+ expect(rendered.container.querySelector("td.error")).toBeNull();
334
+ expect(
335
+ rendered.container.querySelector('input[type="date"]'),
336
+ ).not.toHaveAttribute("min");
337
+ });
338
+
339
+ it("uses unresolved static restrictions for table cell errors too", async () => {
340
+ const column = {
341
+ ...dateField("date"),
342
+ restrictions: { between: restrictions.between },
343
+ };
344
+ const rendered = render(
345
+ <DynamicTableField
346
+ field={tableField([row("2025-01-01")], [column])}
347
+ onChange={jest.fn()}
348
+ />,
349
+ );
350
+ await waitForLoad(rendered);
351
+
352
+ expect(rendered.container.querySelector("td.error")).not.toBeNull();
353
+ });
354
+ });
@@ -0,0 +1,217 @@
1
+ import PropTypes from "prop-types";
2
+ import { Form } from "semantic-ui-react";
3
+ import { useIntl } from "react-intl";
4
+
5
+ const TYPES = [
6
+ { id: "template.field.restrictions.none", value: "none" },
7
+ { id: "template.field.restrictions.max_date", value: "max_date" },
8
+ { id: "template.field.restrictions.min_date", value: "min_date" },
9
+ { id: "template.field.restrictions.between", value: "between" },
10
+ ];
11
+
12
+ const MODES = [
13
+ { id: "template.field.restrictions.mode.static", value: "static" },
14
+ { id: "template.field.restrictions.mode.dynamic", value: "dynamic" },
15
+ ];
16
+
17
+ const UNITS = [
18
+ { id: "template.field.restrictions.units.days", value: "days" },
19
+ { id: "template.field.restrictions.units.weeks", value: "weeks" },
20
+ { id: "template.field.restrictions.units.months", value: "months" },
21
+ { id: "template.field.restrictions.units.years", value: "years" },
22
+ ];
23
+
24
+ const detectType = (restrictions = {}) => {
25
+ if (restrictions.between || restrictions.between_start !== undefined)
26
+ return "between";
27
+ if (restrictions.max_date) return "max_date";
28
+ if (restrictions.min_date) return "min_date";
29
+ return "none";
30
+ };
31
+
32
+ const detectMode = (restrictions = {}, type) => {
33
+ const side =
34
+ type === "between"
35
+ ? restrictions.between
36
+ : type === "none"
37
+ ? undefined
38
+ : restrictions[type];
39
+
40
+ return side?.mode === "dynamic" ? "dynamic" : "static";
41
+ };
42
+
43
+ const emptyFor = (type, mode) => {
44
+ const side = () =>
45
+ mode === "static"
46
+ ? { mode: "static", value: "" }
47
+ : { mode: "dynamic", amount: "", unit: "days" };
48
+
49
+ switch (type) {
50
+ case "max_date":
51
+ case "min_date":
52
+ return { [type]: side() };
53
+ case "between":
54
+ return mode === "static"
55
+ ? { between: { mode: "static", start: "", end: "" } }
56
+ : {
57
+ between: {
58
+ mode: "dynamic",
59
+ from: { amount: "", unit: "days" },
60
+ to: { amount: "", unit: "days" },
61
+ },
62
+ };
63
+ default:
64
+ return {};
65
+ }
66
+ };
67
+
68
+ const options = (formatMessage) => (items) =>
69
+ items.map(({ id, value }) => ({
70
+ key: value,
71
+ text: formatMessage({ id }),
72
+ value,
73
+ }));
74
+
75
+ export const DateRestrictionsForm = ({
76
+ field,
77
+ errors = {},
78
+ fieldNamePrefix,
79
+ onChange,
80
+ }) => {
81
+ const { formatMessage } = useIntl();
82
+
83
+ if (field?.type !== "date" && field?.type !== "datetime") return null;
84
+
85
+ const restrictions = field.restrictions || {};
86
+ const type = detectType(restrictions);
87
+ const mode = detectMode(restrictions, type);
88
+ const isDatetime = field.type === "datetime";
89
+
90
+ const emit = (path, value) =>
91
+ onChange(null, { name: `${fieldNamePrefix}.restrictions.${path}`, value });
92
+
93
+ const handleType = (_, { value }) =>
94
+ onChange(null, {
95
+ name: `${fieldNamePrefix}.restrictions`,
96
+ value: emptyFor(value, "static"),
97
+ });
98
+
99
+ const handleMode = (_, { value }) =>
100
+ onChange(null, {
101
+ name: `${fieldNamePrefix}.restrictions`,
102
+ value: emptyFor(type, value),
103
+ });
104
+
105
+ const typeSelect = (
106
+ <Form.Select
107
+ label={formatMessage({ id: "template.field.restrictions.label" })}
108
+ options={options(formatMessage)(TYPES)}
109
+ value={type}
110
+ onChange={handleType}
111
+ />
112
+ );
113
+
114
+ const modeSelect = (
115
+ <Form.Select
116
+ label={formatMessage({ id: "template.field.restrictions.mode" })}
117
+ options={options(formatMessage)(MODES)}
118
+ value={mode}
119
+ onChange={handleMode}
120
+ error={errors.mode && { content: formatMessage({ id: errors.mode }) }}
121
+ />
122
+ );
123
+
124
+ const valueInput = (path, value, labelId) => (
125
+ <Form.Input
126
+ key={path}
127
+ label={formatMessage({ id: labelId })}
128
+ name={`${fieldNamePrefix}.restrictions.${path}`}
129
+ value={value || ""}
130
+ onChange={handleFieldChange}
131
+ type={isDatetime ? "datetime-local" : "date"}
132
+ error={errors[path] && { content: formatMessage({ id: errors[path] }) }}
133
+ />
134
+ );
135
+
136
+ const handleFieldChange = (e, { name, value }) => {
137
+ e?.preventDefault?.();
138
+ emit(name.split("restrictions.").pop(), value);
139
+ };
140
+
141
+ const amountInput = (path, amount) => (
142
+ <Form.Input
143
+ key={path}
144
+ type="number"
145
+ step="1"
146
+ label={formatMessage({ id: "template.field.restrictions.amount" })}
147
+ name={`${fieldNamePrefix}.restrictions.${path}`}
148
+ value={amount ?? ""}
149
+ onChange={handleFieldChange}
150
+ error={errors[path] && { content: formatMessage({ id: errors[path] }) }}
151
+ />
152
+ );
153
+
154
+ const unitSelect = (path, unit) => (
155
+ <Form.Select
156
+ key={path}
157
+ label={formatMessage({ id: "template.field.restrictions.unit" })}
158
+ name={`${fieldNamePrefix}.restrictions.${path}`}
159
+ options={options(formatMessage)(UNITS)}
160
+ value={unit || ""}
161
+ onChange={handleFieldChange}
162
+ error={errors[path] && { content: formatMessage({ id: errors[path] }) }}
163
+ />
164
+ );
165
+
166
+ const fromLabel = "template.field.restrictions.between.from";
167
+ const toLabel = "template.field.restrictions.between.to";
168
+ const valueLabel = "template.field.restrictions.value";
169
+
170
+ const side = restrictions[type] || {};
171
+ const range = restrictions.between || {};
172
+
173
+ const bounds =
174
+ type === "between"
175
+ ? mode === "static"
176
+ ? [
177
+ valueInput("between.start", range.start, fromLabel),
178
+ valueInput("between.end", range.end, toLabel),
179
+ ]
180
+ : [
181
+ amountInput("between.from.amount", range.from?.amount),
182
+ unitSelect("between.from.unit", range.from?.unit),
183
+ amountInput("between.to.amount", range.to?.amount),
184
+ unitSelect("between.to.unit", range.to?.unit),
185
+ ]
186
+ : mode === "static"
187
+ ? [valueInput(`${type}.value`, side.value, valueLabel)]
188
+ : [
189
+ amountInput(`${type}.amount`, side.amount),
190
+ unitSelect(`${type}.unit`, side.unit),
191
+ ];
192
+
193
+ if (type === "none") {
194
+ return (
195
+ <Form.Group size="small" widths="equal">
196
+ {typeSelect}
197
+ </Form.Group>
198
+ );
199
+ }
200
+
201
+ return (
202
+ <Form.Group size="small" widths="equal">
203
+ {typeSelect}
204
+ {modeSelect}
205
+ {bounds}
206
+ </Form.Group>
207
+ );
208
+ };
209
+
210
+ DateRestrictionsForm.propTypes = {
211
+ field: PropTypes.object,
212
+ errors: PropTypes.object,
213
+ fieldNamePrefix: PropTypes.string.isRequired,
214
+ onChange: PropTypes.func.isRequired,
215
+ };
216
+
217
+ export default DateRestrictionsForm;
@@ -1,9 +1,62 @@
1
1
  import _ from "lodash/fp";
2
2
  import PropTypes from "prop-types";
3
- import { Form } from "semantic-ui-react";
3
+ import { Form, Label } from "semantic-ui-react";
4
4
  import NumberField from "../../../components/widgets/NumberField";
5
5
  import DateTimeField from "../../../components/widgets/DateTimeField";
6
6
  import DateField from "../../../components/widgets/DateField";
7
+ import {
8
+ resolveRestrictions,
9
+ checkValuesAgainstBounds,
10
+ } from "../../utils/dateRestrictions";
11
+
12
+ const DateDefaultField = ({
13
+ Widget,
14
+ field,
15
+ name,
16
+ outOfRange,
17
+ onChange,
18
+ formatMessage,
19
+ }) => {
20
+ const { min, max } = resolveRestrictions(field.restrictions, field.type);
21
+ const defaultValue = _.get("default.value")(field);
22
+ const belowMin = checkValuesAgainstBounds(defaultValue, { min, max });
23
+ const invalidDate = belowMin === "invalid_date";
24
+ const tooEarly = belowMin === "too_early";
25
+
26
+ return (
27
+ <Form.Field error={outOfRange}>
28
+ <Widget
29
+ field={{ ...field, value: _.get("default.value")(field) }}
30
+ label={formatMessage({ id: "template.field.values.default_value" })}
31
+ min={min}
32
+ max={max}
33
+ onChange={(e, { value }) => onChange(e, { name: name, value: value })}
34
+ />
35
+ {outOfRange && !invalidDate && (
36
+ <Label basic color="red" pointing="above">
37
+ {formatMessage(
38
+ {
39
+ id: tooEarly
40
+ ? "template.field.values.default_after_min"
41
+ : "template.field.values.default_before_max",
42
+ },
43
+ { limit: tooEarly ? min : max },
44
+ )}
45
+ </Label>
46
+ )}
47
+ </Form.Field>
48
+ );
49
+ };
50
+
51
+ DateDefaultField.propTypes = {
52
+ Widget: PropTypes.elementType.isRequired,
53
+
54
+ field: PropTypes.object,
55
+ name: PropTypes.string,
56
+ outOfRange: PropTypes.bool,
57
+ onChange: PropTypes.func.isRequired,
58
+ formatMessage: PropTypes.func.isRequired,
59
+ };
7
60
 
8
61
  const defaultValueFormat = (d) => (_.isArray(d) ? _.first(d) : d);
9
62
  const multipleDefaultValueFormat = (d) => {
@@ -57,22 +110,37 @@ export const DefaultValue = ({
57
110
  multipleDefalultsAvaliableWidget.includes(field.widget) &&
58
111
  !multipleDefalultsDisableType.includes(type);
59
112
 
113
+ const dateFieldOutOfRange = (() => {
114
+ if (fieldType !== "date" && fieldType !== "datetime") return false;
115
+ if (_.isEmpty(field.restrictions)) return false;
116
+
117
+ const { min, max } = resolveRestrictions(field.restrictions, fieldType);
118
+ return (
119
+ checkValuesAgainstBounds(_.get("default.value")(field), { min, max }) !==
120
+ "ok"
121
+ );
122
+ })();
123
+
60
124
  return (
61
125
  <>
62
126
  {!type && fieldType === "date" && (
63
- <DateField
64
- field={{ ...field, value: _.get("default.value")(field) }}
65
- onChange={(e, { value }) =>
66
- onDefaultValueChange(e, { name: name, value: value })
67
- }
127
+ <DateDefaultField
128
+ Widget={DateField}
129
+ field={field}
130
+ name={name}
131
+ outOfRange={dateFieldOutOfRange}
132
+ onChange={onDefaultValueChange}
133
+ formatMessage={formatMessage}
68
134
  />
69
135
  )}
70
136
  {!type && fieldType === "datetime" && (
71
- <DateTimeField
72
- field={{ ...field, value: _.get("default.value")(field) }}
73
- onChange={(e, { value }) =>
74
- onDefaultValueChange(e, { name: name, value: value })
75
- }
137
+ <DateDefaultField
138
+ Widget={DateTimeField}
139
+ field={field}
140
+ name={name}
141
+ outOfRange={dateFieldOutOfRange}
142
+ onChange={onDefaultValueChange}
143
+ formatMessage={formatMessage}
76
144
  />
77
145
  )}
78
146
  {!type && (fieldType === "integer" || fieldType === "float") && (