@teselagen/ui 0.7.33-beta.5 → 0.7.33

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 (119) hide show
  1. package/AdvancedOptions.js +33 -0
  2. package/AssignDefaultsModeContext.js +22 -0
  3. package/CellDragHandle.js +132 -0
  4. package/ColumnFilterMenu.js +62 -0
  5. package/Columns.js +979 -0
  6. package/DataTable/utils/queryParams.d.ts +18 -9
  7. package/DisabledLoadingComponent.js +15 -0
  8. package/DisplayOptions.js +199 -0
  9. package/DropdownButton.js +36 -0
  10. package/DropdownCell.js +61 -0
  11. package/EditableCell.js +44 -0
  12. package/FillWindow.css +6 -0
  13. package/FillWindow.js +69 -0
  14. package/FilterAndSortMenu.js +391 -0
  15. package/FormSeparator.js +9 -0
  16. package/LoadingDots.js +14 -0
  17. package/MatchHeaders.js +234 -0
  18. package/PagingTool.js +225 -0
  19. package/RenderCell.js +191 -0
  20. package/SearchBar.js +69 -0
  21. package/SimpleStepViz.js +22 -0
  22. package/SortableColumns.js +100 -0
  23. package/TableFormTrackerContext.js +10 -0
  24. package/Tag.js +112 -0
  25. package/ThComponent.js +44 -0
  26. package/TimelineEvent.js +31 -0
  27. package/UploadCsvWizard.css +4 -0
  28. package/UploadCsvWizard.js +719 -0
  29. package/Uploader.js +1278 -0
  30. package/adHoc.js +10 -0
  31. package/autoTooltip.js +201 -0
  32. package/basicHandleActionsWithFullState.js +14 -0
  33. package/browserUtils.js +3 -0
  34. package/combineReducersWithFullState.js +14 -0
  35. package/commandControls.js +82 -0
  36. package/commandUtils.js +112 -0
  37. package/constants.js +1 -0
  38. package/convertSchema.js +69 -0
  39. package/customIcons.js +361 -0
  40. package/dataTableEnhancer.js +41 -0
  41. package/defaultFormatters.js +32 -0
  42. package/defaultValidators.js +40 -0
  43. package/determineBlackOrWhiteTextColor.js +4 -0
  44. package/editCellHelper.js +44 -0
  45. package/formatPasteData.js +16 -0
  46. package/getAllRows.js +11 -0
  47. package/getCellCopyText.js +7 -0
  48. package/getCellInfo.js +36 -0
  49. package/getCellVal.js +20 -0
  50. package/getDayjsFormatter.js +35 -0
  51. package/getFieldPathToField.js +7 -0
  52. package/getIdOrCodeOrIndex.js +9 -0
  53. package/getLastSelectedEntity.js +11 -0
  54. package/getNewEntToSelect.js +25 -0
  55. package/getNewName.js +31 -0
  56. package/getRowCopyText.js +28 -0
  57. package/getTableConfigFromStorage.js +5 -0
  58. package/getTextFromEl.js +28 -0
  59. package/getVals.js +8 -0
  60. package/handleCopyColumn.js +21 -0
  61. package/handleCopyHelper.js +15 -0
  62. package/handleCopyRows.js +23 -0
  63. package/handleCopyTable.js +16 -0
  64. package/handlerHelpers.js +24 -0
  65. package/hotkeyUtils.js +131 -0
  66. package/index.cjs.js +970 -826
  67. package/index.d.ts +0 -1
  68. package/index.es.js +970 -826
  69. package/index.js +196 -0
  70. package/isBeingCalledExcessively.js +31 -0
  71. package/isBottomRightCornerOfRectangle.js +20 -0
  72. package/isEntityClean.js +15 -0
  73. package/isTruthy.js +12 -0
  74. package/isValueEmpty.js +3 -0
  75. package/itemUpload.js +84 -0
  76. package/menuUtils.js +433 -0
  77. package/package.json +1 -2
  78. package/popoverOverflowModifiers.js +11 -0
  79. package/primarySelectedValue.js +1 -0
  80. package/pureNoFunc.js +31 -0
  81. package/queryParams.js +1058 -0
  82. package/removeCleanRows.js +22 -0
  83. package/renderOnDoc.js +32 -0
  84. package/rerenderOnWindowResize.js +26 -0
  85. package/rowClick.js +181 -0
  86. package/selection.js +8 -0
  87. package/showAppSpinner.js +12 -0
  88. package/showDialogOnDocBody.js +33 -0
  89. package/showProgressToast.js +22 -0
  90. package/sortify.js +73 -0
  91. package/src/DataTable/index.js +1 -1
  92. package/src/DataTable/utils/filterLocalEntitiesToHasura.js +14 -0
  93. package/src/DataTable/utils/filterLocalEntitiesToHasura.test.js +49 -0
  94. package/src/DataTable/utils/queryParams.js +12 -9
  95. package/src/DataTable/utils/tableQueryParamsToHasuraClauses.js +146 -143
  96. package/style.css +29 -0
  97. package/tagUtils.js +45 -0
  98. package/tgFormValues.js +35 -0
  99. package/tg_modalState.js +47 -0
  100. package/throwFormError.js +16 -0
  101. package/toastr.js +148 -0
  102. package/tryToMatchSchemas.js +264 -0
  103. package/typeToCommonType.js +6 -0
  104. package/useDeepEqualMemo.js +15 -0
  105. package/useDialog.js +63 -0
  106. package/useStableReference.js +9 -0
  107. package/useTableEntities.js +38 -0
  108. package/useTraceUpdate.js +19 -0
  109. package/utils.js +37 -0
  110. package/validateTableWideErrors.js +160 -0
  111. package/viewColumn.js +97 -0
  112. package/withField.js +20 -0
  113. package/withFields.js +11 -0
  114. package/withLocalStorage.js +11 -0
  115. package/withSelectTableRecords.js +43 -0
  116. package/withSelectedEntities.js +65 -0
  117. package/withStore.js +10 -0
  118. package/withTableParams.js +301 -0
  119. package/wrapDialog.js +116 -0
@@ -0,0 +1,264 @@
1
+ import { forEach, isArray, isPlainObject, map, snakeCase } from "lodash-es";
2
+ import { nanoid } from "nanoid";
3
+ import Fuse from "fuse.js";
4
+ import { editCellHelper } from "../DataTable/editCellHelper";
5
+ import { validateTableWideErrors } from "../DataTable/validateTableWideErrors";
6
+ import { isEmpty } from "lodash-es";
7
+ import getTextFromEl from "../utils/getTextFromEl";
8
+ import { min } from "lodash-es";
9
+ import { camelCase } from "lodash-es";
10
+ import { startCase } from "lodash-es";
11
+
12
+ const getSchema = data => ({
13
+ fields: map(data[0], (val, path) => {
14
+ return { path, type: "string" };
15
+ }),
16
+ userData: data
17
+ });
18
+
19
+ export default async function tryToMatchSchemas({
20
+ incomingData,
21
+ validateAgainstSchema
22
+ }) {
23
+ await resolveValidateAgainstSchema(validateAgainstSchema);
24
+ const userSchema = getSchema(incomingData);
25
+
26
+ const { searchResults, csvValidationIssue, ignoredHeadersMsg } =
27
+ await matchSchemas({
28
+ userSchema,
29
+ officialSchema: validateAgainstSchema
30
+ });
31
+
32
+ const incomingHeadersToScores = {};
33
+
34
+ searchResults.forEach(r => {
35
+ r.matches.forEach(match => {
36
+ incomingHeadersToScores[match.item.path] =
37
+ incomingHeadersToScores[match.item.path] || [];
38
+ incomingHeadersToScores[match.item.path].push(match.score);
39
+ });
40
+ });
41
+
42
+ searchResults.forEach(r => {
43
+ for (const match of r.matches) {
44
+ if (!incomingHeadersToScores[match.item.path]) continue;
45
+ const minScore = min(incomingHeadersToScores[match.item.path]);
46
+ if (minScore === match.score) {
47
+ r.topMatch = match.item.path;
48
+ r.matches.forEach(match => {
49
+ if (!incomingHeadersToScores[match.item.path]) return;
50
+ const arr = incomingHeadersToScores[match.item.path];
51
+ arr.splice(arr.indexOf(match.score), 1);
52
+ });
53
+ delete incomingHeadersToScores[match.item.path];
54
+ break;
55
+ }
56
+ }
57
+ });
58
+ const matchedHeaders = {};
59
+ searchResults.forEach(r => {
60
+ if (r.topMatch) {
61
+ matchedHeaders[r.path] = r.topMatch;
62
+ }
63
+ });
64
+
65
+ return {
66
+ ignoredHeadersMsg,
67
+ csvValidationIssue,
68
+ matchedHeaders,
69
+ userSchema,
70
+ searchResults
71
+ };
72
+ }
73
+
74
+ async function matchSchemas({ userSchema, officialSchema }) {
75
+ const options = {
76
+ includeScore: true,
77
+ keys: ["path", "displayName"]
78
+ };
79
+ let csvValidationIssue = false;
80
+ const fuse = new Fuse(userSchema.fields, options);
81
+
82
+ const matchedAltPaths = [];
83
+ officialSchema.fields.forEach(h => {
84
+ let hasMatch = false;
85
+ //run fuse search for results
86
+ let result = fuse.search(h.path) || [];
87
+
88
+ const hadNormalPathMatch = userSchema.fields.some(
89
+ uh => norm(uh.path) === norm(h.path)
90
+ );
91
+ //if there are any exact matches, push them onto the results array
92
+ userSchema.fields.forEach((uh, i) => {
93
+ const pathMatch = norm(uh.path) === norm(h.path);
94
+ const displayNameMatch =
95
+ h.displayName && norm(uh.path) === norm(getTextFromEl(h.displayName));
96
+ const hasAlternatePathMatch =
97
+ !hadNormalPathMatch &&
98
+ h.alternatePathMatch &&
99
+ (isArray(h.alternatePathMatch)
100
+ ? h.alternatePathMatch
101
+ : [h.alternatePathMatch]
102
+ ).find(alternatePathMatch => {
103
+ let altPath = alternatePathMatch;
104
+ if (isPlainObject(alternatePathMatch)) {
105
+ altPath = alternatePathMatch.path;
106
+ }
107
+ return norm(uh.path) === norm(altPath);
108
+ });
109
+
110
+ if (hasAlternatePathMatch) {
111
+ matchedAltPaths.push(
112
+ hasAlternatePathMatch.path || hasAlternatePathMatch
113
+ );
114
+ if (hasAlternatePathMatch.format) {
115
+ h.format = hasAlternatePathMatch.format;
116
+ }
117
+ }
118
+ if (pathMatch || displayNameMatch || hasAlternatePathMatch) {
119
+ result = result.filter(({ path }) => path === uh.path);
120
+ //add a fake perfect match result to make sure we get the match
121
+ result.unshift({
122
+ item: {
123
+ path: uh.path,
124
+ type: h.type
125
+ },
126
+ refIndex: i,
127
+ score: 0
128
+ });
129
+ hasMatch = true;
130
+ }
131
+ });
132
+ h.hasMatch = hasMatch;
133
+ h.matches = result;
134
+
135
+ if (!hasMatch && h.isRequired) {
136
+ csvValidationIssue =
137
+ "It looks like some of the headers in your uploaded file(s) do not match the expected headers. Please look over and correct any issues with the mappings below.";
138
+ }
139
+ });
140
+ const ignoredUserSchemaFields = [];
141
+ userSchema.fields.forEach(uh => {
142
+ if (
143
+ !officialSchema.fields.find(
144
+ h =>
145
+ norm(h.path) === norm(uh.path) ||
146
+ norm(h.displayName) === norm(uh.path) ||
147
+ matchedAltPaths.includes(uh.path)
148
+ )
149
+ ) {
150
+ // check that the column does contain data (if it doesn't, it's probably a blank column)
151
+ if (userSchema.userData.some(e => e[uh.path])) {
152
+ ignoredUserSchemaFields.push(uh);
153
+ }
154
+ }
155
+ });
156
+
157
+ if (officialSchema.coerceUserSchema) {
158
+ officialSchema.coerceUserSchema({ userSchema, officialSchema });
159
+ }
160
+
161
+ userSchema.userData = map(userSchema.userData, e => {
162
+ if (!e.id) {
163
+ return {
164
+ ...e,
165
+ id: "__generated__" + nanoid()
166
+ };
167
+ }
168
+ return e;
169
+ });
170
+ const editableFields = officialSchema.fields.filter(f => !f.isNotEditable);
171
+ let hasErr;
172
+ if (!csvValidationIssue) {
173
+ userSchema.userData.some(e => {
174
+ return editableFields.some(columnSchema => {
175
+ //mutative
176
+ const { error } = editCellHelper({
177
+ entity: e,
178
+ columnSchema,
179
+ newVal: columnSchema.hasMatch
180
+ ? e[columnSchema.matches[0]?.item?.path]
181
+ : undefined
182
+ });
183
+ if (error) {
184
+ hasErr = `${
185
+ columnSchema.displayName || startCase(camelCase(columnSchema.path))
186
+ }: ${error}`;
187
+ return true;
188
+ }
189
+
190
+ return false;
191
+ });
192
+ });
193
+ if (!hasErr) {
194
+ hasErr = Object.values(
195
+ validateTableWideErrors({
196
+ entities: userSchema.userData,
197
+ schema: officialSchema,
198
+ optionalUserSchema: userSchema,
199
+ newCellValidate: {}
200
+ })
201
+ )[0];
202
+ }
203
+ }
204
+
205
+ if (officialSchema.tableWideAsyncValidation) {
206
+ //do the table wide validation
207
+ const res = await officialSchema.tableWideAsyncValidation({
208
+ entities: userSchema.userData
209
+ });
210
+ if (!isEmpty(res)) {
211
+ csvValidationIssue = addSpecialPropToAsyncErrs(res);
212
+ }
213
+ //return errors on the tables
214
+ }
215
+
216
+ if (hasErr && !csvValidationIssue) {
217
+ if (hasErr.message) {
218
+ csvValidationIssue = hasErr;
219
+ } else {
220
+ csvValidationIssue = {
221
+ message: hasErr
222
+ };
223
+ // csvValidationIssue = ` Some of the data doesn't look quite right. Do these header mappings look correct?`;
224
+ }
225
+ // csvValidationIssue = `Some of the data doesn't look quite right. Do these header mappings look correct?`;
226
+ }
227
+ let ignoredHeadersMsg;
228
+ if (ignoredUserSchemaFields.length) {
229
+ ignoredHeadersMsg = `It looks like the following headers in your file didn't map to any of the accepted headers: ${ignoredUserSchemaFields
230
+ .map(f => f.displayName || f.path)
231
+ .join(", ")}`;
232
+ }
233
+ return {
234
+ searchResults: officialSchema.fields,
235
+ csvValidationIssue,
236
+ ignoredHeadersMsg
237
+ };
238
+ }
239
+
240
+ export const addSpecialPropToAsyncErrs = res => {
241
+ forEach(res, (v, k) => {
242
+ res[k] = {
243
+ message: v,
244
+ _isTableAsyncWideError: true
245
+ };
246
+ });
247
+ return res;
248
+ };
249
+
250
+ async function resolveValidateAgainstSchema() {
251
+ //tnw: wip!
252
+ // mapSeries(validateAgainstSchema.fields, async f => {
253
+ // if (f.type === "dropdown") {
254
+ // console.log(`type:`, f.type)
255
+ // if (f.getValues) {
256
+ // f.values = await f.getValues(props);
257
+ // }
258
+ // }
259
+ // })
260
+ }
261
+
262
+ function norm(h) {
263
+ return snakeCase(h).toLowerCase().replace(/ /g, "");
264
+ }
@@ -0,0 +1,6 @@
1
+ export const typeToCommonType = {
2
+ string: "Text",
3
+ number: "Number",
4
+ boolean: "True/False",
5
+ dropdown: "Select One"
6
+ };
@@ -0,0 +1,15 @@
1
+ import { isEqual } from "lodash-es";
2
+ import { useEffect, useRef } from "react";
3
+
4
+ export const useDeepEqualMemo = value => {
5
+ const ref = useRef();
6
+ if (!isEqual(value, ref.current)) {
7
+ ref.current = value;
8
+ }
9
+ return ref.current;
10
+ };
11
+
12
+ export const useDeepEqualEffect = (effect, deps) => {
13
+ // eslint-disable-next-line react-hooks/exhaustive-deps
14
+ return useEffect(effect, useDeepEqualMemo(deps));
15
+ };
package/useDialog.js ADDED
@@ -0,0 +1,63 @@
1
+ import React, { useCallback, useState } from "react";
2
+
3
+ /*
4
+
5
+ const {toggleDialog, comp} = useDialog({
6
+ ModalComponent: SimpleInsertData,
7
+ });
8
+
9
+ return <div>
10
+ {comp} //stick the returned dialog comp somewhere in the dom! (it should not effect layout)
11
+ {...your code here}
12
+ </div>
13
+
14
+ */
15
+ export const useDialog = ({ ModalComponent }) => {
16
+ const [isOpen, setOpen] = useState(false);
17
+ const [additionalProps, setAdditionalProps] = useState(false);
18
+ const Comp = useCallback(
19
+ () => (
20
+ <ModalComponent
21
+ hideModal={() => {
22
+ setOpen(false);
23
+ }}
24
+ hideDialog={() => {
25
+ setOpen(false);
26
+ }}
27
+ {...additionalProps}
28
+ dialogProps={{
29
+ isOpen,
30
+ ...additionalProps?.dialogProps
31
+ }}
32
+ />
33
+ ),
34
+ [ModalComponent, additionalProps, isOpen]
35
+ );
36
+
37
+ const showDialogPromise = useCallback(async (handlerName, moreProps = {}) => {
38
+ return new Promise(resolve => {
39
+ //return a promise that can be awaited
40
+ setAdditionalProps({
41
+ hideModal: () => {
42
+ //override hideModal to resolve also
43
+ setOpen(false);
44
+ resolve({});
45
+ },
46
+ hideDialog: () => {
47
+ //override hideModal to resolve also
48
+ setOpen(false);
49
+ resolve({});
50
+ },
51
+ [handlerName]: r => {
52
+ setOpen(false);
53
+ resolve(r || {});
54
+ },
55
+ //pass any additional props to the dialog
56
+ ...moreProps
57
+ });
58
+ setOpen(true); //open the dialog
59
+ });
60
+ }, []);
61
+
62
+ return { Comp, showDialogPromise };
63
+ };
@@ -0,0 +1,9 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ export const useStableReference = value => {
4
+ const ref = useRef();
5
+ useEffect(() => {
6
+ ref.current = value;
7
+ }, [value]);
8
+ return ref;
9
+ };
@@ -0,0 +1,38 @@
1
+ import { useCallback } from "react";
2
+ import { useDispatch, useSelector } from "react-redux";
3
+ import { change, initialize } from "redux-form";
4
+
5
+ export const useTableEntities = tableFormName => {
6
+ const dispatch = useDispatch();
7
+ const selectTableEntities = useCallback(
8
+ (entities = []) => {
9
+ initialize(tableFormName, {}, true, {
10
+ keepDirty: true,
11
+ updateUnregisteredFields: true,
12
+ keepValues: true
13
+ });
14
+ const selectedEntityIdMap = {};
15
+ entities.forEach(entity => {
16
+ selectedEntityIdMap[entity.id] = {
17
+ entity,
18
+ time: Date.now()
19
+ };
20
+ });
21
+ dispatch(
22
+ change(
23
+ tableFormName,
24
+ "reduxFormSelectedEntityIdMap",
25
+ selectedEntityIdMap
26
+ )
27
+ );
28
+ },
29
+ [dispatch, tableFormName]
30
+ );
31
+
32
+ const { allOrderedEntities, selectedEntities } = useSelector(state => ({
33
+ allOrderedEntities: state.form?.[tableFormName]?.values?.allOrderedEntities,
34
+ selectedEntities:
35
+ state.form?.[tableFormName]?.values?.reduxFormSelectedEntityIdMap
36
+ }));
37
+ return { selectTableEntities, allOrderedEntities, selectedEntities };
38
+ };
@@ -0,0 +1,19 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ // useful for debugging rerender errors
4
+ export const useTraceUpdate = props => {
5
+ const prev = useRef(props);
6
+ useEffect(() => {
7
+ const changedProps = Object.entries(props).reduce((ps, [k, v]) => {
8
+ if (prev.current[k] !== v) {
9
+ ps[k] = [prev.current[k], v];
10
+ }
11
+ return ps;
12
+ }, {});
13
+ if (Object.keys(changedProps).length > 0) {
14
+ // eslint-disable-next-line no-console
15
+ console.log("Changed props:", changedProps);
16
+ }
17
+ prev.current = props;
18
+ });
19
+ };
package/utils.js ADDED
@@ -0,0 +1,37 @@
1
+ import { getIdOrCodeOrIndex } from "./getIdOrCodeOrIndex";
2
+
3
+ export const getFieldPathToIndex = schema => {
4
+ const fieldToIndex = {};
5
+ schema.fields.forEach((f, i) => {
6
+ fieldToIndex[f.path] = i;
7
+ });
8
+ return fieldToIndex;
9
+ };
10
+
11
+ export const defaultParsePaste = str => {
12
+ return str.split(/\r\n|\n|\r/).map(row => row.split("\t"));
13
+ };
14
+
15
+ export const getEntityIdToEntity = entities => {
16
+ const entityIdToEntity = {};
17
+ entities.forEach((e, i) => {
18
+ entityIdToEntity[getIdOrCodeOrIndex(e, i)] = { e, i };
19
+ });
20
+ return entityIdToEntity;
21
+ };
22
+
23
+ const endsWithNumber = str => {
24
+ return /[0-9]+$/.test(str);
25
+ };
26
+
27
+ export const getNumberStrAtEnd = str => {
28
+ if (endsWithNumber(str)) {
29
+ return str.match(/[0-9]+$/)[0];
30
+ }
31
+
32
+ return null;
33
+ };
34
+
35
+ export const stripNumberAtEnd = str => {
36
+ return str?.replace?.(getNumberStrAtEnd(str), "");
37
+ };
@@ -0,0 +1,160 @@
1
+ import { getIdOrCodeOrIndex } from "./utils";
2
+ import { getCellVal } from "./getCellVal";
3
+ import { forEach, isArray } from "lodash-es";
4
+ import { startCase } from "lodash-es";
5
+ import { camelCase } from "lodash-es";
6
+
7
+ const uniqueMsg = "This value must be unique";
8
+ export function validateTableWideErrors({
9
+ entities,
10
+ schema,
11
+ optionalUserSchema,
12
+ newCellValidate
13
+ }) {
14
+ forEach(newCellValidate, (err, cellId) => {
15
+ if (err && err._isTableWideError) {
16
+ delete newCellValidate[cellId];
17
+ }
18
+ });
19
+ if (schema.tableWideValidation) {
20
+ const newErrs = schema.tableWideValidation({
21
+ entities
22
+ });
23
+ forEach(newErrs, (err, cellId) => {
24
+ newCellValidate[cellId] = {
25
+ message: err,
26
+ _isTableWideError: true
27
+ };
28
+ });
29
+ }
30
+ const displayNameMap = {};
31
+ const fieldUpperToPath = {};
32
+
33
+ forEach(schema.fields, f => {
34
+ fieldUpperToPath[f.path.toUpperCase()] = f.path;
35
+ displayNameMap[f.path] = f.displayName || startCase(camelCase(f.path));
36
+ });
37
+ function getDisplayName(path) {
38
+ return displayNameMap[path] || startCase(camelCase(path));
39
+ }
40
+
41
+ if (schema.requireAtLeastOneOf) {
42
+ //make sure at least one of the required fields is present
43
+ (isArray(schema.requireAtLeastOneOf[0])
44
+ ? schema.requireAtLeastOneOf
45
+ : [schema.requireAtLeastOneOf]
46
+ ).forEach(alo => {
47
+ entities.forEach(entity => {
48
+ if (!alo.some(path => getCellVal(entity, path))) {
49
+ alo.forEach(path => {
50
+ const cellId = `${getIdOrCodeOrIndex(entity)}:${path}`;
51
+ newCellValidate[cellId] = {
52
+ message: `At least one of these fields must be present - ${alo
53
+ .map(getDisplayName)
54
+ .join(", ")}`,
55
+ _isTableWideError: true
56
+ };
57
+ });
58
+ }
59
+ });
60
+ });
61
+ }
62
+ if (schema.requireExactlyOneOf) {
63
+ (isArray(schema.requireExactlyOneOf[0])
64
+ ? schema.requireExactlyOneOf
65
+ : [schema.requireExactlyOneOf]
66
+ ).forEach(reqs => {
67
+ entities.forEach(entity => {
68
+ const present = reqs.filter(path => getCellVal(entity, path));
69
+ if (present.length !== 1) {
70
+ reqs.forEach(path => {
71
+ const cellId = `${getIdOrCodeOrIndex(entity)}:${path}`;
72
+ const fields = reqs.map(getDisplayName).join(", ");
73
+ const err = {
74
+ message:
75
+ present.length === 0
76
+ ? `One of these fields is required - ${fields}`
77
+ : `Cannot have more than one of these fields - ${fields}`,
78
+ _isTableWideError: true
79
+ };
80
+ newCellValidate[cellId] = err;
81
+ });
82
+ }
83
+ });
84
+ });
85
+ }
86
+
87
+ const requireIfs = [];
88
+ schema.fields.forEach(col => {
89
+ const { path, requireIf } = col;
90
+ if (requireIf) {
91
+ const requireIfPath = fieldUpperToPath[requireIf.toUpperCase()];
92
+ requireIfs.push([requireIfPath, path]);
93
+ }
94
+ });
95
+ requireIfs.forEach(([requireIfPath, path]) => {
96
+ entities.forEach(entity => {
97
+ const requireIfVal = getCellVal(entity, requireIfPath);
98
+ const pathVal = getCellVal(entity, path);
99
+ if (requireIfVal && !pathVal) {
100
+ const cellId = `${getIdOrCodeOrIndex(entity)}:${path}`;
101
+ newCellValidate[cellId] = {
102
+ message: `This field is required if ${displayNameMap[requireIfPath]} is present`,
103
+ _isTableWideError: true
104
+ };
105
+ }
106
+ });
107
+ });
108
+ if (schema.requireAllOrNone) {
109
+ (isArray(schema.requireAllOrNone[0])
110
+ ? schema.requireAllOrNone
111
+ : [schema.requireAllOrNone]
112
+ ).forEach(reqs => {
113
+ entities.forEach(entity => {
114
+ const present = reqs.filter(path => getCellVal(entity, path));
115
+ if (present.length && present.length !== reqs.length) {
116
+ reqs.forEach(path => {
117
+ const cellId = `${getIdOrCodeOrIndex(entity)}:${path}`;
118
+ const fields = reqs.map(getDisplayName).join(", ");
119
+ const err = {
120
+ message: `Please specify either ALL of the following fields or NONE of them - ${fields}`,
121
+ _isTableWideError: true
122
+ };
123
+ newCellValidate[cellId] = err;
124
+ });
125
+ }
126
+ });
127
+ });
128
+ }
129
+
130
+ schema.fields.forEach(col => {
131
+ let { path, isUnique } = col;
132
+ if (isUnique) {
133
+ if (optionalUserSchema) {
134
+ path = col.matches[0].item.path;
135
+ }
136
+ const existingVals = {};
137
+ entities.forEach(entity => {
138
+ const val = getCellVal(entity, path, col);
139
+ if (!val) return;
140
+ const cellId = `${getIdOrCodeOrIndex(entity)}:${path}`;
141
+ if (existingVals[val]) {
142
+ newCellValidate[cellId] = {
143
+ message: uniqueMsg,
144
+ _isTableWideError: true
145
+ };
146
+ newCellValidate[existingVals[val]] = {
147
+ message: uniqueMsg,
148
+ _isTableWideError: true
149
+ };
150
+ } else {
151
+ if (newCellValidate[cellId] === uniqueMsg) {
152
+ delete newCellValidate[cellId];
153
+ }
154
+ existingVals[val] = cellId;
155
+ }
156
+ });
157
+ }
158
+ });
159
+ return newCellValidate;
160
+ }