@onehat/ui 0.4.108 → 0.4.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/ui",
3
- "version": "0.4.108",
3
+ "version": "0.4.109",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -320,7 +320,7 @@ function TagComponent(props) {
320
320
  />;
321
321
  }
322
322
  switch (items.length) {
323
- case 1: height = 250; break;
323
+ case 1: height = 300; break;
324
324
  case 2: height = 400; break;
325
325
  default: height = 600; break;
326
326
  }
@@ -321,7 +321,11 @@ function Form(props) {
321
321
  type = 'Text';
322
322
  }
323
323
  }
324
- const isCombo = type?.match && type.match(/Combo/);
324
+ const
325
+ Element = getComponentFromType(type),
326
+ shouldHideFieldUi = type === 'Hidden',
327
+ isCombo = type?.match && type.match(/Combo/);
328
+
325
329
  if (config.hasOwnProperty('autoLoad')) {
326
330
  editorTypeProps.autoLoad = config.autoLoad;
327
331
  } else {
@@ -335,9 +339,8 @@ function Form(props) {
335
339
  editorTypeProps.showXButton = true;
336
340
  }
337
341
  }
338
- const Element = getComponentFromType(type);
339
342
 
340
- if (isEditorViewOnly || !isEditable) {
343
+ if ((isEditorViewOnly || !isEditable) && !shouldHideFieldUi) {
341
344
  let value = null;
342
345
  if (renderer) {
343
346
  value = renderer(record);
@@ -483,6 +486,9 @@ function Form(props) {
483
486
  {...dynamicProps}
484
487
  className={elementClassName}
485
488
  />;
489
+ if (shouldHideFieldUi) {
490
+ return element;
491
+ }
486
492
 
487
493
  const dirtyIcon = isDirty && !disableDirtyIcon ?
488
494
  <Icon
@@ -532,6 +538,7 @@ function Form(props) {
532
538
  isEditable = true,
533
539
  isEditingEnabledInPlainEditor,
534
540
  label,
541
+ disableLabel = false,
535
542
  labelWidth,
536
543
  items,
537
544
  onChange: onEditorChange,
@@ -588,7 +595,11 @@ function Form(props) {
588
595
  type = 'Text';
589
596
  }
590
597
  }
591
- const isCombo = type?.match && type.match(/Combo/);
598
+ const
599
+ Element = getComponentFromType(type),
600
+ shouldHideFieldUi = type === 'Hidden',
601
+ isCombo = type?.match && type.match(/Combo/);
602
+
592
603
  if (item.hasOwnProperty('autoLoad')) {
593
604
  editorTypeProps.autoLoad = item.autoLoad;
594
605
  } else {
@@ -602,7 +613,6 @@ function Form(props) {
602
613
  editorTypeProps.showXButton = true;
603
614
  }
604
615
  }
605
- const Element = getComponentFromType(type);
606
616
 
607
617
  if (inArray(type, ['Column', 'Row', 'FieldSet'])) {
608
618
  if (_.isEmpty(items)) {
@@ -676,7 +686,7 @@ function Form(props) {
676
686
  label = propertyDef.title;
677
687
  }
678
688
 
679
- if (isEditorViewOnly || !isEditable) {
689
+ if ((isEditorViewOnly || !isEditable) && !shouldHideFieldUi) {
680
690
  let value = null;
681
691
  if (isSingle) {
682
692
  value = record?.properties?.[name]?.displayValue || null;
@@ -713,7 +723,10 @@ function Form(props) {
713
723
  {...viewerTypeProps}
714
724
  className={elementClassName}
715
725
  />;
716
- if (!disableLabels && label) {
726
+ if (shouldHideFieldUi) {
727
+ return null;
728
+ }
729
+ if (!disableLabels && !disableLabel && label) {
717
730
  const style = {};
718
731
  if (defaults?.labelWidth) {
719
732
  style.width = defaults.labelWidth;
@@ -846,6 +859,9 @@ function Form(props) {
846
859
  {...dynamicProps}
847
860
  className={elementClassName}
848
861
  />;
862
+ if (shouldHideFieldUi) {
863
+ return element;
864
+ }
849
865
  let message = null;
850
866
  if (error) {
851
867
  message = error.message;
@@ -905,7 +921,7 @@ function Form(props) {
905
921
  }
906
922
  }
907
923
  const labelToUse = dynamicProps.label || label;
908
- if (!disableLabels && labelToUse && editorType !== EDITOR_TYPE__INLINE) {
924
+ if (!disableLabels && !disableLabel && labelToUse && editorType !== EDITOR_TYPE__INLINE) {
909
925
  const style = {};
910
926
  if (defaults?.labelWidth) {
911
927
  style.width = defaults.labelWidth;
@@ -933,7 +949,7 @@ function Form(props) {
933
949
  {element}
934
950
  </VStack>;
935
951
  }
936
- } else if (disableLabels && requiredIndicator) {
952
+ } else if ((disableLabels || disableLabel) && requiredIndicator) {
937
953
  element = <HStack className="Form-HStack10 w-full">
938
954
  {requiredIndicator}
939
955
  {element}
@@ -1274,7 +1290,10 @@ function Form(props) {
1274
1290
  showCancelBtn = false,
1275
1291
  showSaveBtn = false,
1276
1292
  showSubmitBtn = false,
1277
- isAddMode = getEditorMode() === EDITOR_MODE__ADD;
1293
+ isAddMode = getEditorMode() === EDITOR_MODE__ADD,
1294
+ isEditableMode =
1295
+ getEditorMode() === EDITOR_MODE__ADD ||
1296
+ getEditorMode() === EDITOR_MODE__EDIT;
1278
1297
  if (containerWidth) { // we need to render this component twice in order to get the container width. Skip this on first render
1279
1298
 
1280
1299
  // create editor
@@ -1363,7 +1382,7 @@ function Form(props) {
1363
1382
  if (onDelete && getEditorMode() === EDITOR_MODE__EDIT && isSingle) {
1364
1383
  showDeleteBtn = true;
1365
1384
  }
1366
- if (!isEditorViewOnly && !hideResetButton) {
1385
+ if (!isEditorViewOnly && isEditableMode && !hideResetButton) {
1367
1386
  showResetBtn = true;
1368
1387
  }
1369
1388
  // determine whether we should show the close or cancel button
@@ -1394,7 +1413,7 @@ function Form(props) {
1394
1413
  }
1395
1414
  }
1396
1415
  }
1397
- if (!isEditorViewOnly && onSave) {
1416
+ if (!isEditorViewOnly && isEditableMode && onSave) {
1398
1417
  showSaveBtn = true;
1399
1418
  }
1400
1419
  if (!!onSubmit) {
@@ -62,9 +62,9 @@ export default function withPdfButtons(WrappedComponent) {
62
62
  buildModalItems = () => {
63
63
  // Build a cloned PDF item tree so we never mutate source items by reference.
64
64
  const
65
- itemsTouse = pdfItems || items,
65
+ itemsToUse = pdfItems || _.filter(items, (item) => item?.type !== 'Hidden'),
66
66
  ancillaryItemsToUse = pdfAncillaryItems || ancillaryItems,
67
- modalItems = _.compact(_.map(itemsTouse, (item, ix) => buildNextLayer(item, ix, columnDefaults)));
67
+ modalItems = _.compact(_.map(itemsToUse, (item, ix) => buildNextLayer(item, ix, columnDefaults)));
68
68
 
69
69
  if (!_.isEmpty(ancillaryItemsToUse)) {
70
70
  const
@@ -436,6 +436,10 @@ export default function withPresetButtons(WrappedComponent) {
436
436
  showInfo('Copied to clipboard!');
437
437
  }
438
438
  },
439
+ getColumnsConfigForDownload = () => {
440
+ const activeColumnsConfig = localColumnsConfig.length ? localColumnsConfig : props.columnsConfig;
441
+ return _.filter(activeColumnsConfig, (config) => !config.isHidden);
442
+ },
439
443
  onUploadDownload = () => {
440
444
  const onUploadDecorator = async () => {
441
445
  if (onUpload) {
@@ -454,7 +458,7 @@ export default function withPresetButtons(WrappedComponent) {
454
458
  reference="uploadsDownloads"
455
459
  onClose={hideModal}
456
460
  Repository={Repository}
457
- columnsConfig={props.columnsConfig}
461
+ columnsConfig={getColumnsConfigForDownload()}
458
462
  uploadHeaders={uploadHeaders}
459
463
  uploadParams={uploadParams}
460
464
  onUpload={onUploadDecorator}
@@ -471,7 +475,7 @@ export default function withPresetButtons(WrappedComponent) {
471
475
  onClose={hideModal}
472
476
  isDownloadOnly={true}
473
477
  Repository={Repository}
474
- columnsConfig={props.columnsConfig}
478
+ columnsConfig={getColumnsConfigForDownload()}
475
479
  downloadHeaders={downloadHeaders}
476
480
  downloadParams={downloadParams}
477
481
  />,
@@ -107,6 +107,14 @@ function Viewer(props) {
107
107
  }),
108
108
  isSideEditor = editorType === EDITOR_TYPE__SIDE,
109
109
  isSmartEditor = editorType === EDITOR_TYPE__SMART,
110
+ parentEditorModeRaw = (getEditorMode && getEditorMode()) || editorMode || null,
111
+ parentEditorMode = parentEditorModeRaw === EDITOR_MODE__ADD
112
+ ? EDITOR_MODE__EDIT
113
+ : parentEditorModeRaw,
114
+ normalizedParentEditorMode =
115
+ parentEditorMode === EDITOR_MODE__EDIT || parentEditorMode === EDITOR_MODE__VIEW
116
+ ? parentEditorMode
117
+ : null,
110
118
  styles = UiGlobals.styles,
111
119
  flex = props.flex || 1,
112
120
  buildFromItems = () => {
@@ -125,6 +133,7 @@ function Viewer(props) {
125
133
  title,
126
134
  name,
127
135
  label,
136
+ disableLabel = false,
128
137
  items,
129
138
  useSelectorId = false,
130
139
  isHidden = false,
@@ -160,7 +169,11 @@ function Viewer(props) {
160
169
  type = 'Text';
161
170
  }
162
171
  }
163
- const isCombo = type?.match && type.match(/Combo/);
172
+ const
173
+ Element = getComponentFromType(type),
174
+ shouldHideFieldUi = type === 'Hidden',
175
+ isCombo = type?.match && type.match(/Combo/);
176
+
164
177
  if (item.hasOwnProperty('autoLoad')) {
165
178
  viewerTypeProps.autoLoad = item.autoLoad;
166
179
  } else {
@@ -175,7 +188,6 @@ function Viewer(props) {
175
188
  if (type?.match(/(Grid|GridEditor)$/)) {
176
189
  viewerTypeProps.canEditorViewOnly = true;
177
190
  }
178
- const Element = getComponentFromType(type);
179
191
 
180
192
  if (inArray(type, ['Column', 'Row', 'FieldSet'])) {
181
193
  if (_.isEmpty(items)) {
@@ -264,6 +276,10 @@ function Viewer(props) {
264
276
  >{children}</Element>;
265
277
  }
266
278
 
279
+ if (shouldHideFieldUi) {
280
+ return null;
281
+ }
282
+
267
283
  if (!label && Repository && propertyDef?.title) {
268
284
  label = propertyDef.title;
269
285
  }
@@ -334,7 +350,7 @@ function Viewer(props) {
334
350
  </HStack>;
335
351
  }
336
352
 
337
- if (!disableLabels && label) {
353
+ if (!disableLabels && !disableLabel && label) {
338
354
  const style = {};
339
355
  if (defaults?.labelWidth) {
340
356
  style.width = defaults.labelWidth;
@@ -359,14 +375,6 @@ function Viewer(props) {
359
375
  buildAncillary = () => {
360
376
  const
361
377
  validAncillaryItems = _.filter(ancillaryItems, (item) => !!item), // filter out any null/undefined items
362
- parentEditorModeRaw = (getEditorMode && getEditorMode()) || editorMode || null,
363
- parentEditorMode = parentEditorModeRaw === EDITOR_MODE__ADD
364
- ? EDITOR_MODE__EDIT
365
- : parentEditorModeRaw,
366
- normalizedParentEditorMode =
367
- parentEditorMode === EDITOR_MODE__EDIT || parentEditorMode === EDITOR_MODE__VIEW
368
- ? parentEditorMode
369
- : null,
370
378
  components = [];
371
379
  setAncillaryButtons([]);
372
380
  if (validAncillaryItems.length) {
@@ -584,7 +592,7 @@ function Viewer(props) {
584
592
 
585
593
  <Toolbar className="justify-end">
586
594
  <HStack className="flex-1 items-center">
587
- <Text className="text-[20px] ml-1 text-grey-500">{isEditorModeControlledByParent ? 'View Mode (Inherited)' : 'View Mode'}</Text>
595
+ <Text className="text-[20px] ml-1 text-grey-500">{isEditorModeControlledByParent && normalizedParentEditorMode === EDITOR_MODE__VIEW ? 'View Mode (Inherited)' : 'View Mode'}</Text>
588
596
  </HStack>
589
597
  {onEditMode && (!canUser || canUser(EDIT)) &&
590
598
  <Button
@@ -18,6 +18,11 @@ import Upload from '../Icons/Upload';
18
18
  import Cookies from 'js-cookie';
19
19
  import _ from 'lodash';
20
20
 
21
+ const
22
+ MODES__GRID = 'grid',
23
+ MODES__BATCH = 'batch',
24
+ MODES__TEMPLATE = 'template';
25
+
21
26
  function UploadsDownloadsWindow(props) {
22
27
  const {
23
28
  Repository,
@@ -37,8 +42,8 @@ function UploadsDownloadsWindow(props) {
37
42
  showInfo,
38
43
  } = props,
39
44
  [importFile, setImportFile] = useState(null),
40
- [width, height] = useAdjustedWindowSize(400, 450),
41
- onDownload = (isTemplate = false) => {
45
+ [width, height] = useAdjustedWindowSize(400, 550),
46
+ onDownload = (mode) => {
42
47
 
43
48
  const win = window.open('');
44
49
  win.document.write('<html><head><title>Downloading</title></head><body><img style="position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);" src="' +
@@ -63,7 +68,7 @@ function UploadsDownloadsWindow(props) {
63
68
  columns,
64
69
  order,
65
70
  model,
66
- isTemplate,
71
+ mode,
67
72
  ...Repository._params,
68
73
  ...downloadParams,
69
74
  }),
@@ -79,9 +84,6 @@ function UploadsDownloadsWindow(props) {
79
84
  }
80
85
  }, 1000);
81
86
  },
82
- onDownloadTemplate = () => {
83
- onDownload(true);
84
- },
85
87
  onUploadLocal = async () => {
86
88
  const
87
89
  url = Repository.api.baseURL + Repository.name + '/uploadBatch',
@@ -130,24 +132,40 @@ function UploadsDownloadsWindow(props) {
130
132
  const items = [
131
133
  {
132
134
  type: 'DisplayField',
133
- text: 'Download an Excel file of the current grid contents.',
135
+ text: "Get the current grid's contents as an Excel file.",
136
+ },
137
+ {
138
+ type: 'Button',
139
+ text: 'Get as Excel',
140
+ isEditable: false,
141
+ icon: Excel,
142
+ _icon: {
143
+ size: 'md',
144
+ },
145
+ onPress: () => onDownload(MODES__GRID),
146
+ className: 'mb-5',
147
+ },
148
+ {
149
+ type: 'DisplayField',
150
+ text: "Batch download the current grid's records,\n"
151
+ + "so they can be edited and re-uploaded.",
134
152
  },
135
153
  {
136
154
  type: 'Button',
137
- text: 'Download',
155
+ text: 'Batch Download',
138
156
  isEditable: false,
139
157
  icon: Excel,
140
158
  _icon: {
141
159
  size: 'md',
142
160
  },
143
- onPress: () => onDownload(),
161
+ onPress: () => onDownload(MODES__BATCH),
144
162
  className: 'mb-5',
145
163
  },
146
164
  ];
147
165
  if (!isDownloadOnly) {
148
166
  items.push({
149
167
  type: 'DisplayField',
150
- text: 'Upload an Excel file to the current grid.',
168
+ text: 'Batch upload an Excel file to the current grid.',
151
169
  });
152
170
  items.push({
153
171
  type: 'File',
@@ -161,7 +179,7 @@ function UploadsDownloadsWindow(props) {
161
179
  items: [
162
180
  {
163
181
  type: 'Button',
164
- text: 'Upload',
182
+ text: 'Batch Upload',
165
183
  isEditable: false,
166
184
  icon: Upload,
167
185
  _icon: {
@@ -175,7 +193,7 @@ function UploadsDownloadsWindow(props) {
175
193
  text: 'Get Template',
176
194
  icon: Download,
177
195
  isEditable: false,
178
- onPress: onDownloadTemplate,
196
+ onPress: () => onDownload(MODES__TEMPLATE),
179
197
  },
180
198
 
181
199
  ],
@@ -1,1019 +0,0 @@
1
- import React, { useEffect, useState, useRef, isValidElement, } from 'react';
2
- import {
3
- Box,
4
- HStack,
5
- Icon,
6
- ScrollView,
7
- Text,
8
- TextNative,
9
- VStack,
10
- VStackNative,
11
- } from '@project-components/Gluestack';
12
- import clsx from 'clsx';
13
- import { View, } from 'react-native';
14
- import {
15
- EDITOR_TYPE__INLINE,
16
- EDITOR_TYPE__WINDOWED,
17
- EDITOR_TYPE__SIDE,
18
- EDITOR_TYPE__SMART,
19
- EDITOR_TYPE__PLAIN,
20
- EDITOR_MODE__VIEW,
21
- EDITOR_MODE__ADD,
22
- EDITOR_MODE__EDIT,
23
- } from '../../../Constants/Editor.js';
24
- import { Form, Formik, Field } from "formik"; // https://formik.org/docs/overview
25
- import { useForm, Controller } from 'react-hook-form'; // https://react-hook-form.com/api/
26
- import * as yup from 'yup'; // https://github.com/jquense/yup#string
27
- import { yupResolver } from '@hookform/resolvers/yup';
28
- import useForceUpdate from '../../../Hooks/useForceUpdate.js';
29
- import UiGlobals from '../../../UiGlobals.js';
30
- import withAlert from '../../../Hoc/withAlert.js';
31
- import withComponent from '../../../Hoc/withComponent.js';
32
- import withEditor from '../../../Hoc/withEditor.js';
33
- import withPdfButton from '../../../Hoc/withPdfButton.js';
34
- import inArray from '../../../Functions/inArray.js';
35
- import getComponentFromType from '../../../Functions/getComponentFromType.js';
36
- import buildAdditionalButtons from '../../../Functions/buildAdditionalButtons.js';
37
- import Button from '../../../Buttons/Button.js';
38
- import IconButton from '../../../Buttons/IconButton.js';
39
- import AngleLeft from '../../../Icons/AngleLeft.js';
40
- import Eye from '../../../Icons/Eye.js';
41
- import Rotate from '../../../Icons/Rotate.js';
42
- import Pencil from '../../../Icons/Pencil.js';
43
- import Footer from '../../../Layout/Footer.js';
44
- import Label from '../../../Form/Label.js';
45
- import _ from 'lodash';
46
-
47
- // TODO: memoize field Components
48
-
49
- // Modes:
50
- // EDITOR_TYPE__INLINE
51
- // Form is a single scrollable row, based on columnsConfig and Repository
52
- //
53
- // EDITOR_TYPE__WINDOWED
54
- // EDITOR_TYPE__SIDE
55
- // Form is a popup or side window, used for editing items in a grid. Integrated with Repository
56
- //
57
- // EDITOR_TYPE__SMART
58
- // Form is a standalone editor
59
- //
60
- // EDITOR_TYPE__PLAIN
61
- // Form is embedded on screen in some other way. Mainly use startingValues, items, validator
62
-
63
- function FormikForm(props) {
64
- const
65
- {
66
- editorType = EDITOR_TYPE__WINDOWED, // EDITOR_TYPE__INLINE | EDITOR_TYPE__WINDOWED | EDITOR_TYPE__SIDE | EDITOR_TYPE__SMART | EDITOR_TYPE__PLAIN
67
- startingValues = {},
68
- items = [], // Columns, FieldSets, Fields, etc to define the form
69
- ancillaryItems = [], // additional items which are not controllable form elements, but should appear in the form
70
- columnDefaults = {}, // defaults for each Column defined in items (above)
71
- columnsConfig, // Which columns are shown in Grid, so the inline editor can match. Used only for EDITOR_TYPE__INLINE
72
- validator, // custom validator, mainly for EDITOR_TYPE__PLAIN
73
- footerProps = {},
74
- buttonGroupProps = {}, // buttons in footer
75
- checkIsEditingDisabled = true,
76
- disableLabels = false,
77
- disableDirtyIcon = false,
78
- onBack,
79
- onReset,
80
- onInit,
81
- onViewMode,
82
- submitBtnLabel,
83
- onSubmit,
84
- formSetup, // this fn will be executed after the form setup is complete
85
- additionalEditButtons,
86
- useAdditionalEditButtons = true,
87
- additionalFooterButtons,
88
-
89
- // sizing of outer container
90
- h,
91
- maxHeight,
92
- minHeight = 0,
93
- w,
94
- maxWidth,
95
- flex,
96
- onLayout, // onLayout handler for main view
97
-
98
- // withComponent
99
- self,
100
-
101
- // withData
102
- Repository,
103
-
104
- // withEditor
105
- isEditorViewOnly = false,
106
- isSaving = false,
107
- editorMode,
108
- onCancel,
109
- onSave,
110
- onClose,
111
- onDelete,
112
- editorStateRef,
113
- disableView,
114
-
115
- // parent container
116
- selectorId,
117
- selectorSelected,
118
-
119
- // withAlert
120
- alert,
121
- } = props,
122
- formRef = useRef(),
123
- styles = UiGlobals.styles,
124
- record = props.record?.length === 1 ? props.record[0] : props.record;
125
- let skipAll = false;
126
- if (record?.isDestroyed) {
127
- skipAll = true; // if record is destroyed, skip render, but allow hooks to still be called
128
- if (self?.parent?.parent?.setIsEditorShown) {
129
- self.parent.parent.setIsEditorShown(false); // close the editor
130
- }
131
- }
132
- const
133
- isMultiple = _.isArray(record),
134
- isSingle = !isMultiple, // for convenience
135
- isPhantom = !skipAll && !!record?.isPhantom, //
136
- forceUpdate = useForceUpdate(),
137
- [previousRecord, setPreviousRecord] = useState(record),
138
- [containerWidth, setContainerWidth] = useState(),
139
- initialValues = _.merge(startingValues, (record && !record.isDestroyed ? record.submitValues : {})),
140
- defaultValues = isMultiple ? getNullFieldValues(initialValues, Repository) : initialValues, // when multiple entities, set all default values to null
141
- validatorToUse = validator || (isMultiple ? disableRequiredYupFields(Repository?.schema?.model?.validator) : Repository?.schema?.model?.validator) || yup.object(),
142
- {
143
- control,
144
- formState,
145
- handleSubmit,
146
- // register,
147
- // unregister,
148
- reset,
149
- // watch,
150
- // resetField,
151
- // setError,
152
- // clearErrors,
153
- setValue: formSetValue,
154
- // setFocus,
155
- getValues: formGetValues,
156
- // getFieldState,
157
- trigger,
158
- } = useForm({
159
- mode: 'onChange', // onChange | onBlur | onSubmit | onTouched | all
160
- // reValidateMode: 'onChange', // onChange | onBlur | onSubmit
161
- defaultValues,
162
- // values: defaultValues,
163
- // resetOptions: {
164
- // keepDirtyValues: false, // user-interacted input will be retained
165
- // keepErrors: false, // input errors will be retained with value update
166
- // },
167
- // criteriaMode: 'firstError', // firstError | all
168
- // shouldFocusError: false,
169
- // delayError: 0,
170
- // shouldUnregister: false,
171
- // shouldUseNativeValidation: false,
172
- resolver: yupResolver(validatorToUse),
173
- context: { isPhantom },
174
- }),
175
- buildFromColumnsConfig = () => {
176
- // For InlineEditor
177
- // Build the fields that match the current columnsConfig in the grid
178
- const
179
- model = Repository.getSchema().model,
180
- elements = [],
181
- columnProps = {
182
- justifyContent: 'center',
183
- alignItems: 'center',
184
- borderRightWidth: 1,
185
- borderRightColor: 'grey-200',
186
- px: 1,
187
- };
188
-
189
- if (editorType === EDITOR_TYPE__INLINE) {
190
- columnProps.minWidth = styles.INLINE_EDITOR_MIN_WIDTH;
191
- }
192
-
193
- _.each(columnsConfig, (config, ix) => {
194
- let {
195
- fieldName,
196
- isEditable,
197
- editor,
198
- renderer,
199
- w,
200
- flex,
201
- useSelectorId = false,
202
- } = config;
203
-
204
- if (!isEditable) {
205
- let renderedValue = renderer ? renderer(record) : record[fieldName];
206
- if (_.isBoolean(renderedValue)) {
207
- renderedValue = renderedValue.toString();
208
- }
209
- renderedValue += "\n(not editable)";
210
- elements.push(<Box key={ix} {...columnProps} className={` flex-${flex} w-${w} `}>
211
- <TextNative numberOfLines={1} ellipsizeMode="head">{renderedValue}</TextNative>
212
- </Box>);
213
- } else {
214
- elements.push(<Controller
215
- key={'controller-' + ix}
216
- name={fieldName}
217
- // rules={rules}
218
- control={control}
219
- render={(args) => {
220
- const {
221
- field,
222
- fieldState,
223
- // formState,
224
- } = args,
225
- {
226
- onChange,
227
- onBlur,
228
- name,
229
- value,
230
- // ref,
231
- } = field,
232
- {
233
- isTouched,
234
- isDirty,
235
- error,
236
- } = fieldState;
237
- let _editor = {};
238
- if (!editor) {
239
- const propertyDef = fieldName && Repository?.getSchema().getPropertyDefinition(fieldName);
240
- editor = propertyDef && propertyDef[fieldName].editorType;
241
- if (_.isPlainObject(editor)) {
242
- const {
243
- type,
244
- onChange: onEditorChange,
245
- ...p
246
- } = editor;
247
- _editor = p;
248
- editor = type;
249
- }
250
- }
251
- const Element = getComponentFromType(editor);
252
-
253
- if (useSelectorId) {
254
- _editor.selectorId = selectorId;
255
- _editor.selectorSelected = _editor;
256
- }
257
-
258
- let element = <Element
259
- name={name}
260
- value={value}
261
- setValue={(newValue) => {
262
- onChange(newValue);
263
- if (onEditorChange) {
264
- onEditorChange(newValue, formSetValue, formGetValues, formState);
265
- }
266
- }}
267
- onBlur={onBlur}
268
- flex={1}
269
- {..._editor}
270
- parent={self}
271
- reference={fieldName}
272
- // {...defaults}
273
- // {...propsToPass}
274
- />;
275
-
276
- // element = <Tooltip key={ix} label={header} placement="bottom">
277
- // {element}
278
- // </Tooltip>;
279
- // if (error) {
280
- // element = <VStack className="pt-1 flex-1">
281
- // {element}
282
- // <Text color="#f00">{error.message}</Text>
283
- // </VStack>;
284
- // }
285
-
286
- const dirtyIcon = isDirty && !disableDirtyIcon ? <Icon
287
- as={Pencil}
288
- size="2xs"
289
- className="text-grey-300 absolute top-[2px] left-[2px]"
290
- /> : null;
291
- return <HStack
292
- key={ix}
293
- {...columnProps}
294
- className={` flex-${flex} w-${w} ${error ? "bg-[#fdd]" : "bg-white"} `}
295
- >{dirtyIcon}{element}</HStack>;
296
- }}
297
- />);
298
- }
299
- });
300
- return <HStack>{elements}</HStack>;
301
- },
302
- buildFromItems = () => {
303
- return _.map(items, (item, ix) => buildFromItem(item, ix, columnDefaults));
304
- },
305
- buildFromItem = (item, ix, defaults) => {
306
- if (!item) {
307
- return null;
308
- }
309
- if (React.isValidElement(item)) {
310
- return item;
311
- }
312
- let {
313
- type,
314
- title,
315
- name,
316
- isEditable = true,
317
- label,
318
- items,
319
- onChange: onEditorChange,
320
- useSelectorId = false,
321
- isHidden = false,
322
- getDynamicProps,
323
- getIsRequired,
324
- ...propsToPass
325
- } = item,
326
- editorTypeProps = {};
327
-
328
- if (isHidden) {
329
- return null;
330
- }
331
- if (type === 'DisplayField') {
332
- isEditable = false;
333
- }
334
- const propertyDef = name && Repository?.getSchema().getPropertyDefinition(name);
335
- if (!useAdditionalEditButtons) {
336
- item = _.omit(item, 'additionalEditButtons');
337
- }
338
- if (propertyDef?.isEditingDisabled && checkIsEditingDisabled) {
339
- isEditable = false;
340
- }
341
- if (!type) {
342
- if (isEditable) {
343
- const
344
- {
345
- type: t,
346
- ...p
347
- } = propertyDef?.editorType;
348
- type = t;
349
- editorTypeProps = p;
350
- } else if (propertyDef?.viewerType) {
351
- const
352
- {
353
- type: t,
354
- ...p
355
- } = propertyDef?.viewerType;
356
- type = t;
357
- } else {
358
- type = 'Text';
359
- }
360
- }
361
- const isCombo = type?.match && type.match(/Combo/);
362
- if (item.hasOwnProperty('autoLoad')) {
363
- editorTypeProps.autoLoad = item.autoLoad;
364
- } else {
365
- if (isCombo && Repository?.isRemote && !Repository?.isLoaded) {
366
- editorTypeProps.autoLoad = true;
367
- }
368
- }
369
- if (isCombo) {
370
- // editorTypeProps.showEyeButton = true;
371
- if (_.isNil(propsToPass.showXButton)) {
372
- editorTypeProps.showXButton = true;
373
- }
374
- }
375
- const Element = getComponentFromType(type);
376
- let children;
377
-
378
- if (inArray(type, ['Column', 'Row', 'FieldSet'])) {
379
- if (_.isEmpty(items)) {
380
- return null;
381
- }
382
- if (type === 'Column') {
383
- if (containerWidth < styles.FORM_ONE_COLUMN_THRESHOLD) {
384
- // everything is in one column
385
- if (propsToPass.hasOwnProperty('flex')) {
386
- delete propsToPass.flex;
387
- }
388
- if (propsToPass.hasOwnProperty('width')) {
389
- delete propsToPass.width;
390
- }
391
- if (propsToPass.hasOwnProperty('w')) {
392
- delete propsToPass.w;
393
- }
394
- propsToPass.w = '100%';
395
- propsToPass.mb = 1;
396
- }
397
- propsToPass.pl = 3;
398
- }
399
- if (type === 'Row') {
400
- propsToPass.w = '100%';
401
- }
402
- const itemDefaults = item.defaults;
403
- children = _.map(items, (item, ix) => {
404
- return buildFromItem(item, ix, itemDefaults);
405
- });
406
- return <Element key={ix} title={title} {...itemDefaults} {...propsToPass} {...editorTypeProps}>{children}</Element>;
407
- }
408
-
409
- if (!label && Repository && propertyDef?.title) {
410
- label = propertyDef.title;
411
- }
412
-
413
- if (isEditorViewOnly || !isEditable) {
414
- let value = null;
415
- if (record?.properties && record.properties[name]) {
416
- value = record.properties[name].displayValue;
417
- }
418
- if (_.isNil(value) && record && record[name]) {
419
- value = record[name];
420
- }
421
- if (_.isNil(value) && startingValues && startingValues[name]) {
422
- value = startingValues[name];
423
- }
424
-
425
- let element = <Element
426
- value={value}
427
- parent={self}
428
- reference={name}
429
- {...propsToPass}
430
- />;
431
- if (!disableLabels && label) {
432
- const labelProps = {};
433
- if (defaults?.labelWidth) {
434
- labelProps.w = defaults.labelWidth;
435
- }
436
- if (containerWidth > styles.FORM_STACK_ROW_THRESHOLD) {
437
- element = <><Label {...labelProps}>{label}</Label>{element}</>;
438
- } else {
439
- element = <VStack><Label {...labelProps}>{label}</Label>{element}</VStack>;
440
- }
441
- }
442
- return <HStack key={ix} className="px-2 pb-1">{element}</HStack>;
443
- }
444
-
445
-
446
-
447
- // // These rules are for fields *outside* the model
448
- // // but which want validation on the form anyway.
449
- // // The useForm() resolver disables this
450
- // const
451
- // rules = {},
452
- // rulesToCheck = [
453
- // 'required',
454
- // 'min',
455
- // 'max',
456
- // 'minLength',
457
- // 'maxLength',
458
- // 'pattern',
459
- // 'validate',
460
- // ];
461
- // _.each(rulesToCheck, (rule) => {
462
- // if (item.hasOwnProperty(rule)) {
463
- // rules[rule] = item[rule];
464
- // }
465
- // });
466
-
467
- return <Controller
468
- key={'controller-' + ix}
469
- name={name}
470
- // rules={rules}
471
- control={control}
472
- render={(args) => {
473
- const {
474
- field,
475
- fieldState,
476
- // formState,
477
- } = args,
478
- {
479
- onChange,
480
- onBlur,
481
- name,
482
- value,
483
- // ref,
484
- } = field,
485
- {
486
- isTouched,
487
- isDirty,
488
- error,
489
- } = fieldState;
490
- if (isValidElement(Element)) {
491
- throw new Error('Should not yet be valid React element. Did you use <Element> instead of () => <Element> when defining it?')
492
- }
493
-
494
- if (useSelectorId) { // This causes the whole form to use selectorId
495
- editorTypeProps.selectorId = selectorId;
496
- }
497
- if (propsToPass.selectorId || editorTypeProps.selectorId) { // editorTypeProps.selectorId causes just this one field to use selectorId
498
- if (_.isNil(propsToPass.selectorSelected)) {
499
- editorTypeProps.selectorSelected = record;
500
- }
501
- }
502
- let dynamicProps = {};
503
- if (getDynamicProps) {
504
- dynamicProps = getDynamicProps({ fieldState, formSetValue, formGetValues, formState });
505
- }
506
- let element = <Element
507
- name={name}
508
- value={value}
509
- onChangeValue={(newValue) => {
510
- if (newValue === undefined) {
511
- newValue = null; // React Hook Form doesn't respond well when setting value to undefined
512
- }
513
- onChange(newValue);
514
- if (onEditorChange) {
515
- onEditorChange(newValue, formSetValue, formGetValues, formState, trigger);
516
- }
517
- }}
518
- onBlur={onBlur}
519
- flex={1}
520
- parent={self}
521
- reference={name}
522
- {...defaults}
523
- {...propsToPass}
524
- {...editorTypeProps}
525
- {...dynamicProps}
526
- />;
527
- if (editorType !== EDITOR_TYPE__INLINE) {
528
- let message = null;
529
- if (error) {
530
- message = error.message;
531
- if (label && error.ref?.name) {
532
- message = message.replace(error.ref.name, label);
533
- }
534
- }
535
- if (message) {
536
- message = <Text className="text-[#f00]">{message}</Text>;
537
- }
538
- element = <VStack className="pt-1 flex-1">
539
- {element}
540
- {message}
541
- </VStack>;
542
- }
543
-
544
- if (item.additionalEditButtons) {
545
- const buttons = buildAdditionalButtons(item.additionalEditButtons, self, { fieldState, formSetValue, formGetValues, formState });
546
- if (containerWidth > styles.FORM_STACK_ROW_THRESHOLD) {
547
- element = <HStack className="flex-1 flex-wrap">
548
- {element}
549
- {buttons}
550
- </HStack>;
551
- } else {
552
- element = <VStack className="flex-1 w-full">
553
- {element}
554
- <HStack className="flex-1 w-full mt-2 flex-wrap">
555
- {buttons}
556
- </HStack>
557
- </VStack>;
558
- }
559
- }
560
-
561
- let isRequired = false,
562
- requiredIndicator = null;
563
- if (!isMultiple) { // Don't require fields if editing multiple records
564
- if (getIsRequired) {
565
- isRequired = getIsRequired(formGetValues, formState);
566
- } else if (validatorToUse?.fields && validatorToUse.fields[name]?.exclusiveTests?.required) {
567
- // submitted validator
568
- isRequired = true;
569
- } else if ((propertyDef?.validator?.spec && !propertyDef.validator.spec.optional) ||
570
- (propertyDef?.requiredIfPhantom && isPhantom) ||
571
- (propertyDef?.requiredIfNotPhantom && !isPhantom)) {
572
- // property definition
573
- isRequired = true;
574
- }
575
- if (isRequired) {
576
- requiredIndicator = <Text className="text-[#f00] text-[30px] pr-1">*</Text>;
577
- }
578
- }
579
- if (!disableLabels && label && editorType !== EDITOR_TYPE__INLINE) {
580
- const labelProps = {};
581
- if (defaults?.labelWidth) {
582
- labelProps.w = defaults.labelWidth;
583
- }
584
- if (containerWidth > styles.FORM_STACK_ROW_THRESHOLD) {
585
- element = <HStack className="w-full py-1">
586
- <Label {...labelProps}>{requiredIndicator}{label}</Label>
587
- {element}
588
- </HStack>;
589
- } else {
590
- element = <VStack className="w-full py-1 mt-3">
591
- <Label {...labelProps}>{requiredIndicator}{label}</Label>
592
- {element}
593
- </VStack>;
594
- }
595
- } else if (disableLabels && requiredIndicator) {
596
- element = <HStack className="w-full py-1">
597
- {requiredIndicator}
598
- {element}
599
- </HStack>;
600
- }
601
-
602
- const dirtyIcon = isDirty && !disableDirtyIcon ? <Icon
603
- as={Pencil}
604
- size="2xs"
605
- className="text-grey-300 absolute top-[2px] left-[2px]" /> : null;
606
- return (
607
- <HStack
608
- key={ix}
609
- className={` ${error ? "bg-[#fdd]" : "bg-[null]"} px-2 pb-1 `}>{dirtyIcon}{element}</HStack>
610
- );
611
- }}
612
- />;
613
- },
614
- buildAncillary = () => {
615
- const components = [];
616
- if (ancillaryItems.length) {
617
- _.each(ancillaryItems, (item, ix) => {
618
- let {
619
- type,
620
- title = null,
621
- description = null,
622
- selectorId,
623
- ...propsToPass
624
- } = item;
625
- if (isMultiple && type !== 'Attachments') {
626
- return;
627
- }
628
- if (!propsToPass.h) {
629
- propsToPass.h = 400;
630
- }
631
- const
632
- Element = getComponentFromType(type),
633
- element = <Element
634
- selectorId={selectorId}
635
- selectorSelected={selectorSelected || record}
636
- flex={1}
637
- uniqueRepository={true}
638
- parent={self}
639
- {...propsToPass}
640
- />;
641
- if (title) {
642
- if (record?.displayValue) {
643
- title += ' for ' + record.displayValue;
644
- }
645
- title = <Text
646
- className={` ${styles.FORM_ANCILLARY_TITLE_CLASSNAME} font-bold `}
647
- >{title}</Text>;
648
- }
649
- if (description) {
650
- description = <Text
651
- className={` ${styles.FORM_ANCILLARY_DESCRIPTION_CLASSNAME} italic-italic `}
652
- >{description}</Text>;
653
- }
654
- components.push(<VStack key={'ancillary-' + ix} className="mx-1 my-3">{title}{description}{element}</VStack>);
655
- });
656
- }
657
- return components;
658
- },
659
- onSubmitError = (errors, e) => {
660
- if (editorType === EDITOR_TYPE__INLINE) {
661
- alert(errors.message);
662
- }
663
- },
664
- doReset = (values) => {
665
- reset(values);
666
- if (onReset) {
667
- onReset(values, formSetValue, formGetValues);
668
- }
669
- },
670
- onSaveDecorated = async (data, e) => {
671
- // reset the form after a save
672
- const result = await onSave(data, e);
673
- if (result) {
674
- const values = record.submitValues;
675
- doReset(values);
676
- }
677
- },
678
- onSubmitDecorated = async (data, e) => {
679
- const result = await onSubmit(data, e);
680
- if (result) {
681
- const values = record.submitValues;
682
- doReset(values);
683
- }
684
- },
685
- onLayoutDecorated = (e) => {
686
- if (onLayout) {
687
- onLayout(e);
688
- }
689
-
690
- setContainerWidth(e.nativeEvent.layout.width);
691
- };
692
-
693
- useEffect(() => {
694
- if (skipAll) {
695
- return;
696
- }
697
- if (record === previousRecord) {
698
- if (onInit) {
699
- onInit(initialValues, formSetValue, formGetValues);
700
- }
701
- } else {
702
- setPreviousRecord(record);
703
- doReset(defaultValues);
704
- }
705
- if (formSetup) {
706
- formSetup(formSetValue, formGetValues, formState)
707
- }
708
- }, [record]);
709
-
710
- useEffect(() => {
711
- if (skipAll) {
712
- return;
713
- }
714
- if (!Repository) {
715
- return () => {
716
- if (!_.isNil(editorStateRef)) {
717
- editorStateRef.current = null; // clean up the editorStateRef on unmount
718
- }
719
- };
720
- }
721
-
722
- Repository.ons(['changeData', 'change'], forceUpdate);
723
-
724
- return () => {
725
- Repository.offs(['changeData', 'change'], forceUpdate);
726
- if (!_.isNil(editorStateRef)) {
727
- editorStateRef.current = null; // clean up the editorStateRef on unmount
728
- }
729
- };
730
- }, [Repository]);
731
-
732
- if (skipAll) {
733
- return null;
734
- }
735
-
736
- // if (Repository && (!record || _.isEmpty(record) || record.isDestroyed)) {
737
- // return null;
738
- // }
739
-
740
- if (!_.isNil(editorStateRef)) {
741
- editorStateRef.current = formState; // Update state so HOC can know what's going on
742
- }
743
-
744
- if (self) {
745
- self.ref = formRef;
746
- self.formState = formState;
747
- self.formSetValue = formSetValue;
748
- self.formGetValues = formGetValues;
749
- }
750
-
751
- const sizeProps = {};
752
- if (!flex && !h && !w) {
753
- sizeProps.flex = 1;
754
- } else {
755
- if (h) {
756
- sizeProps.h = h;
757
- }
758
- if (w) {
759
- sizeProps.w = w;
760
- }
761
- if (flex) {
762
- sizeProps.flex = flex;
763
- }
764
- }
765
- if (maxWidth) {
766
- sizeProps.maxWidth = maxWidth;
767
- }
768
- if (maxHeight) {
769
- sizeProps.maxHeight = maxHeight;
770
- }
771
-
772
- const formButtons = [];
773
- let formComponents,
774
- editor,
775
- additionalButtons,
776
- isSaveDisabled = false,
777
- isSubmitDisabled = false,
778
- savingProps = {},
779
-
780
- showDeleteBtn = false,
781
- showResetBtn = false,
782
- showCloseBtn = false,
783
- showCancelBtn = false,
784
- showSaveBtn = false,
785
- showSubmitBtn = false;
786
-
787
- if (containerWidth) { // we need to render this component twice in order to get the container width. Skip this on first render
788
-
789
- if (isSaving) {
790
- savingProps.borderTopWidth = 2;
791
- savingProps.borderTopColor = '#f00';
792
- }
793
-
794
- if (editorType === EDITOR_TYPE__INLINE) {
795
- editor = buildFromColumnsConfig();
796
- // } else if (editorType === EDITOR_TYPE__PLAIN) {
797
- // formComponents = buildFromItems();
798
- // const formAncillaryComponents = buildAncillary();
799
- // editor = <>
800
- // <VStack className="p-4">{formComponents}</VStack>
801
- // <VStack className="pt-4">{formAncillaryComponents}</VStack>
802
- // </>;
803
- } else {
804
- formComponents = buildFromItems();
805
- const formAncillaryComponents = buildAncillary();
806
- editor = <>
807
- {containerWidth >= styles.FORM_ONE_COLUMN_THRESHOLD ? <HStack className="p-4 pl-0">{formComponents}</HStack> : null}
808
- {containerWidth < styles.FORM_ONE_COLUMN_THRESHOLD ? <VStack className="p-4">{formComponents}</VStack> : null}
809
- <VStack className="m-2 pt-4 px-2">{formAncillaryComponents}</VStack>
810
- </>;
811
-
812
- additionalButtons = buildAdditionalButtons(additionalEditButtons);
813
-
814
- formButtons.push(<HStack key="buttonsRow" className="px-4 pt-4 items-center justify-end">
815
- {isSingle && editorMode === EDITOR_MODE__EDIT && onBack &&
816
- <Button
817
- key="backBtn"
818
- onPress={onBack}
819
- leftIcon={<Icon as={AngleLeft} size="sm" className="text-white" />}
820
- color="#fff"
821
- >Back</Button>}
822
- {isSingle && editorMode === EDITOR_MODE__EDIT && onViewMode && !disableView &&
823
- <Button
824
- key="viewBtn"
825
- onPress={onViewMode}
826
- leftIcon={<Icon as={Eye} size="sm" className="text-white" />}
827
- color="#fff"
828
- >To View</Button>}
829
- </HStack>);
830
- if (editorMode === EDITOR_MODE__EDIT && !_.isEmpty(additionalButtons)) {
831
- formButtons.push(<HStack
832
- key="additionalButtonsRow"
833
- className="p-[4px] items-center justify-end flex-wrap"
834
- >
835
- {additionalButtons}
836
- </HStack>)
837
- }
838
- }
839
-
840
- if (!formState.isValid) {
841
- isSaveDisabled = true;
842
- isSubmitDisabled = true;
843
- }
844
- if (_.isEmpty(formState.dirtyFields) && !isPhantom) {
845
- isSaveDisabled = true;
846
- }
847
-
848
- if (editorType === EDITOR_TYPE__INLINE) {
849
- buttonGroupProps.position = 'fixed';
850
- buttonGroupProps.left = 10; // TODO: I would prefer to have this be centered, but it's a lot more complex than just making it stick to the left
851
- footerProps.alignItems = 'flex-start';
852
- }
853
-
854
- if (onDelete && editorMode === EDITOR_MODE__EDIT && isSingle) {
855
- showDeleteBtn = true;
856
- }
857
- if (!isEditorViewOnly) {
858
- showResetBtn = true;
859
- }
860
- if (editorType !== EDITOR_TYPE__SIDE) { // side editor won't show either close or cancel buttons!
861
- // determine whether we should show the close or cancel button
862
- if (isEditorViewOnly) {
863
- showCloseBtn = true;
864
- } else {
865
- const formIsDirty = formState.isDirty;
866
- // console.log('formIsDirty', formIsDirty);
867
- // console.log('isPhantom', isPhantom);
868
- if (formIsDirty || isPhantom) {
869
- if (isSingle && onCancel) {
870
- showCancelBtn = true;
871
- }
872
- } else {
873
- if (onClose) {
874
- showCloseBtn = true;
875
- }
876
- }
877
- }
878
- }
879
- if (!isEditorViewOnly && onSave) {
880
- showSaveBtn = true;
881
- }
882
- if (!!onSubmit) {
883
- showSubmitBtn = true;
884
- }
885
- }
886
-
887
- return <VStackNative
888
- {...sizeProps}
889
- onLayout={onLayoutDecorated}
890
- ref={formRef}
891
- >
892
- {!!containerWidth && <>
893
- {editorType === EDITOR_TYPE__INLINE &&
894
- <ScrollView
895
- horizontal={true}
896
- className="flex-1 bg-white py-1 border-t-[3px] border-b-[5px] border-t-primary-100 border-b-primary-100">{editor}</ScrollView>}
897
- {editorType !== EDITOR_TYPE__INLINE &&
898
- <ScrollView _web={{ minHeight, }} className="w-full pb-1">
899
- {formButtons}
900
- {editor}
901
- </ScrollView>}
902
-
903
- <Footer className="justify-end" {...footerProps} {...savingProps}>
904
- {onDelete && editorMode === EDITOR_MODE__EDIT && isSingle &&
905
-
906
- <HStack className="flex-1 justify-start">
907
- <Button
908
- key="deleteBtn"
909
- onPress={onDelete}
910
- bg="warning"
911
- _hover={{
912
- bg: 'warningHover',
913
- }}
914
- color="#fff"
915
- >Delete</Button>
916
- </HStack>}
917
-
918
- {showResetBtn &&
919
- <IconButton
920
- key="resetBtn"
921
- onPress={() => doReset()}
922
- icon={Rotate}
923
- _icon={{
924
- color: !formState.isDirty ? 'grey-400' : '#000',
925
- }}
926
- isDisabled={!formState.isDirty}
927
- mr={2}
928
- />}
929
-
930
- {showCancelBtn &&
931
- <Button
932
- key="cancelBtn"
933
- variant="outline"
934
- onPress={onCancel}
935
- color="#fff"
936
- >Cancel</Button>}
937
-
938
- {showCloseBtn &&
939
- <Button
940
- key="closeBtn"
941
- variant="outline"
942
- onPress={onClose}
943
- color="#fff"
944
- >Close</Button>}
945
-
946
- {showSaveBtn &&
947
- <Button
948
- key="saveBtn"
949
- onPress={(e) => handleSubmit(onSaveDecorated, onSubmitError)(e)}
950
- isDisabled={isSaveDisabled}
951
- color="#fff"
952
- >{editorMode === EDITOR_MODE__ADD ? 'Add' : 'Save'}</Button>}
953
-
954
- {showSubmitBtn &&
955
- <Button
956
- key="submitBtn"
957
- onPress={(e) => handleSubmit(onSubmitDecorated, onSubmitError)(e)}
958
- isDisabled={isSubmitDisabled}
959
- color="#fff"
960
- >{submitBtnLabel || 'Submit'}</Button>}
961
-
962
- {additionalFooterButtons && _.map(additionalFooterButtons, (props) => {
963
- return <Button
964
- {...props}
965
- onPress={(e) => handleSubmit(props.onPress, onSubmitError)(e)}
966
- >{props.text}</Button>;
967
- })}
968
- </Footer>
969
- </>}
970
- </VStackNative>;
971
- }
972
-
973
- // helper fns
974
- function disableRequiredYupFields(validator) {
975
- // based on https://github.com/jquense/yup/issues/1466#issuecomment-944386480
976
- if (!validator) {
977
- return null;
978
- }
979
-
980
- const nextSchema = validator.clone();
981
- return nextSchema.withMutation((next) => {
982
- if (typeof next.fields === 'object' && next.fields != null) {
983
- for (const key in next.fields) {
984
- const nestedField = next.fields[key];
985
-
986
- let nestedFieldNext = nestedField.notRequired();
987
-
988
- if (Array.isArray(nestedField.conditions) && nestedField.conditions.length > 0) {
989
- // Next is done to disable required() inside a condition
990
- // https://github.com/jquense/yup/issues/1002
991
- nestedFieldNext = nestedFieldNext.when('whatever', (unused, schema) => {
992
- return schema.notRequired();
993
- });
994
- }
995
-
996
- next.fields[key] = nestedFieldNext;
997
- }
998
- }
999
- });
1000
- }
1001
- function getNullFieldValues(initialValues, Repository) {
1002
- const ret = {};
1003
- if (Repository) {
1004
- const properties = Repository.getSchema().model.properties;
1005
- _.each(properties, (propertyDef) => {
1006
- ret[propertyDef.name] = null;
1007
- });
1008
- } else {
1009
- // takes a JSON object of fieldValues and sets them all to null
1010
- _.each(initialValues, (value, field) => {
1011
- ret[field] = null;
1012
- });
1013
- }
1014
- return ret;
1015
- }
1016
-
1017
- export const FormEditor = withComponent(withAlert(withEditor(withPdfButton(FormikForm))));
1018
-
1019
- export default withComponent(withAlert(withPdfButton(FormikForm)));