@onehat/ui 0.4.44 → 0.4.47

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.44",
3
+ "version": "0.4.47",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -722,7 +722,7 @@ export const ComboComponent = forwardRef((props, ref) => {
722
722
  // 'data',
723
723
  'idIx',
724
724
  'displayIx',
725
- 'value',
725
+ // 'value',
726
726
  'disableView',
727
727
  'disableCopy',
728
728
  'disableDuplicate',
@@ -732,6 +732,9 @@ export const ComboComponent = forwardRef((props, ref) => {
732
732
  'selectorSelectedField',
733
733
  'usePermissions',
734
734
  ]);
735
+ if (!isInTag) {
736
+ gridProps.value = props.value;
737
+ }
735
738
  if (!Repository) {
736
739
  gridProps.data = filteredData;
737
740
  }
@@ -17,6 +17,7 @@ import {
17
17
  UI_MODE_NATIVE,
18
18
  UI_MODE_WEB,
19
19
  } from '../../../Constants/UiModes.js';
20
+ import Button from '../../Buttons/Button.js';
20
21
  import UiGlobals from '../../../UiGlobals.js';
21
22
  import Formatters from '@onehat/data/src/Util/Formatters.js';
22
23
  import Parsers from '@onehat/data/src/Util/Parsers.js';
@@ -219,6 +220,9 @@ export const DateElement = forwardRef((props, ref) => {
219
220
  setBothValues(value);
220
221
  setTextInputValue(value);
221
222
  }
223
+ },
224
+ onToday = () => {
225
+ onPickerChange(moment());
222
226
  };
223
227
 
224
228
  useEffect(() => {
@@ -460,6 +464,15 @@ export const DateElement = forwardRef((props, ref) => {
460
464
  timeFormat={mode === TIME || mode === DATETIME ? 'HH:mm:ss' : false}
461
465
  onChange={onPickerChange}
462
466
  />
467
+ <Button
468
+ {...testProps('todayBtn')}
469
+ key="todayBtn"
470
+ onPress={onToday}
471
+ className={`
472
+ mt-2
473
+ `}
474
+ text="Today"
475
+ />
463
476
  </PopoverBody>
464
477
  </PopoverContent>
465
478
  </Popover>;
@@ -148,6 +148,7 @@ function SliderElement(props) {
148
148
  }
149
149
 
150
150
  let className = `
151
+ Slider
151
152
  w-full
152
153
  items-center
153
154
  `,
@@ -184,7 +185,7 @@ function SliderElement(props) {
184
185
  tooltipPlacement={tooltipPlacement}
185
186
  {...props._input}
186
187
  />
187
- <HStack className="flex-1">
188
+ <HStack className="SliderContainer flex-1">
188
189
  <Slider
189
190
  {...testProps('slider')}
190
191
  ref={props.outerRef}
@@ -38,6 +38,9 @@ function TagComponent(props) {
38
38
  // withComponent
39
39
  self,
40
40
 
41
+ // withFilters
42
+ isInFilter,
43
+
41
44
  // withValue
42
45
  value = [],
43
46
  setValue,
@@ -246,6 +249,9 @@ function TagComponent(props) {
246
249
  w-full
247
250
  p-0
248
251
  `;
252
+ if (isInFilter) {
253
+ className += ' max-w-[250px]';
254
+ }
249
255
  if (props.className) {
250
256
  className += ' ' + props.className;
251
257
  }
@@ -465,13 +465,15 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
465
465
  }
466
466
 
467
467
  setIsSaving(true);
468
- let success;
469
- try {
470
- await SecondaryRepository.save(null, useStaged);
471
- success = true;
472
- } catch (e) {
473
- success = e;
474
- }
468
+ let success = true;
469
+ const tempListener = (msg, data) => {
470
+ success = { msg, data };
471
+ };
472
+
473
+ SecondaryRepository.on('error', tempListener); // add a temporary listener for the error event
474
+ await SecondaryRepository.save(null, useStaged);
475
+ SecondaryRepository.off('error', tempListener); // remove the temporary listener
476
+
475
477
  setIsSaving(false);
476
478
 
477
479
  if (_.isBoolean(success) && success) {
@@ -499,7 +501,10 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
499
501
  secondaryOnSave(secondarySelection);
500
502
  }
501
503
  }
502
- // setIsEditorShown(false);
504
+ if (secondaryEditorType === EDITOR_TYPE__INLINE) {
505
+ secondarySetIsEditorShown(false);
506
+ }
507
+
503
508
  }
504
509
 
505
510
  return success;
@@ -62,6 +62,7 @@ function withAlert(WrappedComponent) {
62
62
  text-${color}
63
63
  text-[18px]
64
64
  flex-none
65
+ mr-2
65
66
  `}>{message}</Text>
66
67
  </Box>
67
68
  </HStack>;
@@ -500,13 +500,15 @@ export default function withEditor(WrappedComponent, isTree = false) {
500
500
  }
501
501
 
502
502
  setIsSaving(true);
503
- let success;
504
- try {
505
- await Repository.save(null, useStaged);
506
- success = true;
507
- } catch (e) {
508
- success = e;
509
- }
503
+ let success = true;
504
+ const tempListener = (msg, data) => {
505
+ success = { msg, data };
506
+ };
507
+
508
+ Repository.on('error', tempListener); // add a temporary listener for the error event
509
+ await Repository.save(null, useStaged);
510
+ Repository.off('error', tempListener); // remove the temporary listener
511
+
510
512
  setIsSaving(false);
511
513
 
512
514
  if (_.isBoolean(success) && success) {
@@ -534,7 +536,9 @@ export default function withEditor(WrappedComponent, isTree = false) {
534
536
  onSave(selection);
535
537
  }
536
538
  }
537
- // setIsEditorShown(false);
539
+ if (editorType === EDITOR_TYPE__INLINE) {
540
+ setIsEditorShown(false);
541
+ }
538
542
  }
539
543
 
540
544
  return success;
@@ -307,7 +307,7 @@ export default function withFilters(WrappedComponent) {
307
307
  placeholder={tooltip}
308
308
  value={getFilterValue(field)}
309
309
  onChangeValue={(value) => onFilterChangeValue(field, value)}
310
- isFilter={true}
310
+ isInFilter={true}
311
311
  minimizeForRow={true}
312
312
  {...filterProps}
313
313
  {...elementProps}
@@ -45,10 +45,6 @@ function UploadsDownloadsWindow(props) {
45
45
 
46
46
  const
47
47
  baseURL = Repository.api.baseURL,
48
- filters = Repository.filters.reduce((result, current) => {
49
- result[current.name] = current.value;
50
- return result;
51
- }, {}),
52
48
  columns = columnsConfig.map((column) => {
53
49
  return column.fieldName;
54
50
  }),
@@ -62,11 +58,11 @@ function UploadsDownloadsWindow(props) {
62
58
  body: JSON.stringify({
63
59
  download_token,
64
60
  report_id: 1,
65
- filters,
66
61
  columns,
67
62
  order,
68
63
  model,
69
64
  isTemplate,
65
+ ...Repository._params,
70
66
  ...downloadParams,
71
67
  }),
72
68
  headers: _.merge({ 'Content-Type': 'application/json' }, Repository.headers, downloadHeaders),
@@ -21,12 +21,17 @@ import {
21
21
  import { Avatar, Dropzone, FileMosaic, FileCard, FileInputButton, } from "@files-ui/react";
22
22
  import inArray from '../../Functions/inArray.js';
23
23
  import IconButton from '../../Components/Buttons/IconButton.js';
24
- import Xmark from '../../Components/Icons/Xmark.js'
24
+ import Xmark from '../../Components/Icons/Xmark.js';
25
+ import Eye from '../../Components/Icons/Eye.js';
26
+ import ChevronLeft from '../../Components/Icons/ChevronLeft.js';
27
+ import ChevronRight from '../../Components/Icons/ChevronRight.js';
25
28
  import withAlert from '../../Components/Hoc/withAlert.js';
26
29
  import withComponent from '../../Components/Hoc/withComponent.js';
27
30
  import withData from '../../Components/Hoc/withData.js';
31
+ import CenterBox from '../../Components/Layout/CenterBox.js';
28
32
  import downloadInBackground from '../../Functions/downloadInBackground.js';
29
33
  import downloadWithFetch from '../../Functions/downloadWithFetch.js';
34
+ import useForceUpdate from '../../Hooks/useForceUpdate.js';
30
35
  import _ from 'lodash';
31
36
 
32
37
  const
@@ -35,16 +40,18 @@ const
35
40
  isPwa = !!window?.navigator?.standalone;
36
41
 
37
42
  function FileCardCustom(props) {
38
- const
39
- {
43
+ const {
40
44
  id,
41
45
  name: filename,
42
46
  type: mimetype,
43
47
  onDelete,
48
+ onSee,
44
49
  downloadUrl,
45
50
  uploadStatus,
46
51
  } = props,
47
- isDownloading = uploadStatus && inArray(uploadStatus, ['preparing', 'uploading', 'success']);
52
+ isDownloading = uploadStatus && inArray(uploadStatus, ['preparing', 'uploading', 'success']),
53
+ isPdf = mimetype === 'application/pdf';
54
+
48
55
  return <Pressable
49
56
  onPress={() => {
50
57
  downloadInBackground(downloadUrl);
@@ -52,6 +59,7 @@ function FileCardCustom(props) {
52
59
  className="px-3 py-1 items-center flex-row rounded-[5px] border border-primary.700"
53
60
  >
54
61
  {isDownloading && <Spinner className="mr-2" />}
62
+ {onSee && isPdf && <IconButton mr={1} icon={Eye} onPress={() => onSee(null, id)} />}
55
63
  <Text>{filename}</Text>
56
64
  {onDelete && <IconButton ml={1} icon={Xmark} onPress={() => onDelete(id)} />}
57
65
  </Pressable>;
@@ -81,7 +89,7 @@ function AttachmentsElement(props) {
81
89
  expandedMax = EXPANDED_MAX,
82
90
  collapsedMax = COLLAPSED_MAX,
83
91
  autoUpload = true,
84
- onBeforeDropzoneChange,
92
+ onAfterDropzoneChange, // fn, should return true if it mutated the files array
85
93
 
86
94
  // withComponent
87
95
  self,
@@ -94,6 +102,8 @@ function AttachmentsElement(props) {
94
102
  Repository,
95
103
 
96
104
  // withAlert
105
+ showModal,
106
+ updateModalBody,
97
107
  alert,
98
108
  confirm,
99
109
 
@@ -102,6 +112,7 @@ function AttachmentsElement(props) {
102
112
  model = _.isArray(selectorSelected) && selectorSelected[0] ? selectorSelected[0].repository?.name : selectorSelected?.repository?.name,
103
113
  modelidCalc = _.isArray(selectorSelected) ? _.map(selectorSelected, (entity) => entity[selectorSelectedField]) : selectorSelected?.[selectorSelectedField],
104
114
  modelid = useRef(modelidCalc),
115
+ forceUpdate = useForceUpdate(),
105
116
  [isReady, setIsReady] = useState(false),
106
117
  [isUploading, setIsUploading] = useState(false),
107
118
  [showAll, setShowAll] = useState(false),
@@ -135,7 +146,7 @@ function AttachmentsElement(props) {
135
146
  toggleShowAll = () => {
136
147
  setShowAll(!showAll);
137
148
  },
138
- onDropzoneChange = (files) => {
149
+ onDropzoneChange = async (files) => {
139
150
  if (!files.length) {
140
151
  alert('No files accepted. Perhaps they were too large or the wrong file type?');
141
152
  return;
@@ -148,8 +159,11 @@ function AttachmentsElement(props) {
148
159
  ...extraUploadData,
149
160
  };
150
161
  });
151
- if (onBeforeDropzoneChange) {
152
- onBeforeDropzoneChange(files);
162
+ if (onAfterDropzoneChange) {
163
+ const isChanged = await onAfterDropzoneChange(files);
164
+ if (isChanged) {
165
+ forceUpdate();
166
+ }
153
167
  }
154
168
  },
155
169
  onUploadStart = (files) => {
@@ -199,9 +213,120 @@ function AttachmentsElement(props) {
199
213
  downloadInBackground(url);
200
214
  }
201
215
  },
216
+ buildModalBody = (url, id) => {
217
+ // This method was abstracted out so showModal/onPrev/onNext can all use it.
218
+ // url comes from FileMosaic, which passes in imageUrl,
219
+ // whereas FileCardCustom passes in id.
220
+
221
+ function findFile(url, id) {
222
+ if (id) {
223
+ return _.find(files, { id });
224
+ }
225
+ return _.find(files, (file) => file.imageUrl === url);
226
+ }
227
+ function findPrevFile(url, id) {
228
+ const
229
+ currentFile = findFile(url, id),
230
+ currentIx = _.findIndex(files, currentFile);
231
+ if (currentIx > 0) {
232
+ return files[currentIx - 1];
233
+ }
234
+ return null;
235
+ }
236
+ function findNextFile(url, id) {
237
+ const
238
+ currentFile = findFile(url, id),
239
+ currentIx = _.findIndex(files, currentFile);
240
+ if (currentIx < files.length - 1) {
241
+ return files[currentIx + 1];
242
+ }
243
+ return null;
244
+ }
245
+
246
+ const
247
+ prevFile = findPrevFile(url, id),
248
+ isPrevDisabled = !prevFile,
249
+ nextFile = findNextFile(url, id),
250
+ isNextDisabled = !nextFile,
251
+ onPrev = () => {
252
+ const { imageUrl, id } = prevFile;
253
+ updateModalBody(buildModalBody(imageUrl, id));
254
+ },
255
+ onNext = () => {
256
+ const { imageUrl, id } = nextFile;
257
+ updateModalBody(buildModalBody(imageUrl, id));
258
+ };
259
+
260
+ let isPdf = false,
261
+ body = null;
262
+
263
+ if (id) {
264
+ const file = _.find(files, { id });
265
+ url = file.imageUrl;
266
+ isPdf = true;
267
+ } else if (url?.match(/\.pdf$/)) {
268
+ isPdf = true;
269
+ }
270
+
271
+ if (isPdf) {
272
+ body = <iframe
273
+ src={url}
274
+ className="w-full h-full"
275
+ />;
276
+ } else {
277
+ body = <CenterBox className="w-full h-full">
278
+ <img src={url} />
279
+ </CenterBox>;
280
+ }
281
+ return <HStack
282
+ className="w-full h-full"
283
+ >
284
+ <IconButton
285
+ onPress={onPrev}
286
+ className="Lightbox-prevBtn h-full w-[50px]"
287
+ icon={ChevronLeft}
288
+ isDisabled={isPrevDisabled}
289
+ />
290
+ {body}
291
+ <IconButton
292
+ onPress={onNext}
293
+ className="Lightbox-prevBtn h-full w-[50px]"
294
+ icon={ChevronRight}
295
+ isDisabled={isNextDisabled}
296
+ />
297
+ </HStack>;
298
+ },
299
+ onViewLightbox = (url, id) => {
300
+ if (!url && !id) {
301
+ alert('Cannot view lightbox until image is uploaded.');
302
+ return;
303
+ }
304
+ showModal({
305
+ title: 'Lightbox',
306
+ body: buildModalBody(url, id),
307
+ canClose: true,
308
+ includeCancel: true,
309
+ w: 1920,
310
+ h: 1080,
311
+ });
312
+ },
202
313
  doDelete = (id) => {
203
- Repository.deleteById(id);
204
- Repository.save();
314
+ const file = Repository.getById(id);
315
+ if (file) {
316
+ // if the file exists in the repository, delete it there
317
+ Repository.deleteById(id);
318
+ Repository.save();
319
+
320
+ } else {
321
+ // simply remove it from the files array
322
+ const newFiles = [];
323
+ _.each(files, (file) => {
324
+ if (file.id !== id) {
325
+ newFiles.push(file);
326
+ }
327
+ });
328
+ setFiles(newFiles);
329
+ }
205
330
  };
206
331
 
207
332
  if (!_.isEqual(modelidCalc, modelid.current)) {
@@ -296,6 +421,12 @@ function AttachmentsElement(props) {
296
421
  <HStack className="AttachmentsElement-HStack flex-wrap">
297
422
  {files.length === 0 && <Text className="text-grey-600 italic">No files</Text>}
298
423
  {files.map((file) => {
424
+ let seeProps = {};
425
+ if (file.type && (file.type.match(/^image\//) || file.type === 'application/pdf')) {
426
+ seeProps = {
427
+ onSee: onViewLightbox,
428
+ };
429
+ }
299
430
  return <Box
300
431
  key={file.id}
301
432
  className="mr-2"
@@ -306,12 +437,14 @@ function AttachmentsElement(props) {
306
437
  backgroundBlurImage={false}
307
438
  onDownload={onDownload}
308
439
  {..._fileMosaic}
440
+ {...seeProps}
309
441
  />}
310
442
  {!useFileMosaic &&
311
443
  <FileCardCustom
312
444
  {...file}
313
445
  backgroundBlurImage={false}
314
446
  {..._fileMosaic}
447
+ {...seeProps}
315
448
  />}
316
449
  </Box>;
317
450
  })}
File without changes