@onehat/ui 0.4.101 → 0.4.103

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 (51) hide show
  1. package/package.json +1 -1
  2. package/src/Components/Accordion/Accordion.js +65 -6
  3. package/src/Components/Container/Container.js +10 -4
  4. package/src/Components/Form/Field/Combo/Combo.js +10 -4
  5. package/src/Components/Form/Field/Tag/Tag.js +6 -0
  6. package/src/Components/Form/Form.js +8 -3
  7. package/src/Components/Grid/Grid.js +232 -154
  8. package/src/Components/Grid/GridRow.js +7 -3
  9. package/src/Components/Hoc/withPresetButtons.js +18 -6
  10. package/src/Components/Icons/ArrowsLeftRight.js +10 -0
  11. package/src/Components/Icons/Bar.js +10 -0
  12. package/src/Components/Icons/Box.js +11 -0
  13. package/src/Components/Icons/BoxOpen.js +11 -0
  14. package/src/Components/Icons/Bucket.js +10 -0
  15. package/src/Components/Icons/Bump.js +21 -0
  16. package/src/Components/Icons/Calculator.js +12 -0
  17. package/src/Components/Icons/Dots.js +20 -0
  18. package/src/Components/Icons/Fleets.js +26 -0
  19. package/src/Components/Icons/Lock.js +11 -0
  20. package/src/Components/Icons/Microchip.js +12 -0
  21. package/src/Components/Icons/Num1.js +10 -0
  22. package/src/Components/Icons/Num2.js +10 -0
  23. package/src/Components/Icons/Num3.js +10 -0
  24. package/src/Components/Icons/Num4.js +10 -0
  25. package/src/Components/Icons/OilCan.js +11 -0
  26. package/src/Components/Icons/Operations.js +10 -0
  27. package/src/Components/Icons/OverduePms.js +10 -0
  28. package/src/Components/Icons/SackDollar.js +11 -0
  29. package/src/Components/Icons/ShortBar.js +15 -0
  30. package/src/Components/Icons/Tower.js +10 -0
  31. package/src/Components/Icons/UpcomingPms.js +10 -0
  32. package/src/Components/Layout/ScreenHeader.js +35 -3
  33. package/src/Components/Layout/SetupButton.js +31 -0
  34. package/src/Components/Layout/UserIndicator.js +35 -0
  35. package/src/Components/Pms/Editor/BumpPmsEditor.js +9 -0
  36. package/src/Components/Pms/Editor/MetersEditor.js +173 -0
  37. package/src/Components/Pms/Editor/PmEventsEditor.js +291 -0
  38. package/src/Components/Pms/Grid/UpcomingPmsGrid.js +569 -0
  39. package/src/Components/Pms/Layout/TreeSpecific/MakeTreeSelection.js +11 -0
  40. package/src/Components/Pms/Layout/TreeSpecific/TreeSpecific.js +30 -0
  41. package/src/Components/Pms/Modals/BulkAssignTechnician.js +104 -0
  42. package/src/Components/Pms/Screens/PmsManager.js +136 -0
  43. package/src/Components/Pms/Window/BumpPmsEditorWindow.js +25 -0
  44. package/src/Components/Screens/Manager.js +3 -0
  45. package/src/Components/Screens/ReportsManager.js +51 -26
  46. package/src/Components/Tree/Tree.js +15 -6
  47. package/src/Components/Viewer/PmCalcDebugViewer.js +164 -146
  48. package/src/Components/Viewer/TextWithLinks.js +9 -1
  49. package/src/Components/Viewer/Viewer.js +38 -30
  50. package/src/Functions/flatten.js +39 -0
  51. package/src/Functions/verifyCanCrudPmEvents.js +33 -0
@@ -0,0 +1,569 @@
1
+ import { useState, useEffect, } from 'react';
2
+ import {
3
+ VStack,
4
+ } from '@project-components/Gluestack';
5
+ import clsx from 'clsx';
6
+ import {
7
+ PM_EVENT_TYPES__DELAY_BY_DAYS,
8
+ } from '../../../Constants/PmEventTypes.js';
9
+ import {
10
+ PM_STATUSES__PM_DUE,
11
+ PM_STATUSES__DELAYED,
12
+ PM_STATUSES__WILL_CALL,
13
+ PM_STATUSES__OVERDUE,
14
+ } from '../../../Constants/PmStatuses.js';
15
+ import {
16
+ EDITOR_TYPE__WINDOWED,
17
+ } from '../../../Constants/Editor.js';
18
+ import {
19
+ WO_CLASSES__PM,
20
+ } from '@src/Constants/WoClasses.js';
21
+ import oneHatData from '@onehat/data';
22
+ import Grid from '../../Grid/Grid.js';
23
+ import Loading from '../../Messages/Loading.js';
24
+ import IconButton from '../../Buttons/IconButton.js';
25
+ import ScreenHeader from '../../Layout/ScreenHeader.js';
26
+ import withAlert from '../../Hoc/withAlert.js';
27
+ import withComponent from '../../Hoc/withComponent.js';
28
+ import withData from '../../Hoc/withData.js';
29
+ import withSelection from '../../Hoc/withSelection.js';
30
+ import withWindowedEditor from '../../Hoc/withWindowedEditor.js';
31
+ import useAdjustedWindowSize from '../../../Hooks/useAdjustedWindowSize.js';
32
+ import Calculator from '../../Icons/Calculator.js';
33
+ import Clipboard from '../../Icons/Clipboard.js';
34
+ import OilCan from '../../Icons/OilCan.js';
35
+ import Bump from '../../Icons/Bump.js';
36
+ import BumpPmsEditorWindow from '../Window/BumpPmsEditorWindow.js';
37
+ import PmCalcDebugViewer from '../../Viewer/PmCalcDebugViewer.js';
38
+ import {
39
+ EquipmentIcon,
40
+ } from '@src/Components/Icons/index';
41
+ import EquipmentEditor from '@src/Components/Editor/EquipmentEditor.js';
42
+ import UiGlobals from '../../../UiGlobals.js';
43
+
44
+
45
+ function UpcomingPmsGrid(props) {
46
+ const {
47
+ onAdd: onAddBumper,
48
+ addWorkOrder,
49
+ editWorkOrder,
50
+ setWorkOrderIsIgnoreNextSelectionChange,
51
+ setWorkOrderSelection,
52
+ setWithEditListeners,
53
+ setIsEditorShown,
54
+ nodeId,
55
+ nodeType,
56
+
57
+ // withAlert
58
+ alert,
59
+
60
+ // withComponent
61
+ self,
62
+
63
+ // withModal
64
+ showModal,
65
+ hideModal,
66
+
67
+ // config
68
+ includeWorkOrderButton = false,
69
+ getRowProps = (item) => {
70
+ const rowProps = {
71
+ borderBottomWidth: 1,
72
+ borderBottomColor: 'trueGray.500',
73
+ };
74
+ if (item.meters_pm_schedules__pm_status_id === PM_STATUSES__PM_DUE) {
75
+ rowProps.bg = '#f9eabb';
76
+ }
77
+ if (item.meters_pm_schedules__pm_status_id === PM_STATUSES__DELAYED) {
78
+ rowProps.bg = '#d4fed2';
79
+ }
80
+ if (item.meters_pm_schedules__pm_status_id === PM_STATUSES__WILL_CALL) {
81
+ rowProps.bg = '#cdf1ff';
82
+ }
83
+ if (item.meters_pm_schedules__pm_status_id === PM_STATUSES__OVERDUE) {
84
+ rowProps.bg = '#ffd1d1';
85
+ }
86
+ return rowProps;
87
+ },
88
+ } = props,
89
+ styles = UiGlobals.styles,
90
+ UpcomingPms = oneHatData.getRepository('UpcomingPms'),
91
+ [Equipment] = useState(() => oneHatData.getRepository('Equipment', true)),
92
+ [WorkOrders] = useState(() => oneHatData.getRepository('WorkOrders', true)),
93
+ [isReady, setIsReady] = useState(false),
94
+ [width, height] = useAdjustedWindowSize(styles.DEFAULT_WINDOW_WIDTH, styles.DEFAULT_WINDOW_HEIGHT),
95
+ onBump = async (metersPmSchedule) => {
96
+ setWithEditListeners({
97
+ onAfterAddSave: () => {
98
+ setIsEditorShown(false);
99
+ },
100
+ });
101
+ onAddBumper(null, {
102
+ pm_events__meter_id: metersPmSchedule.meters_pm_schedules__meter_id,
103
+ pm_events__pm_schedule_id: metersPmSchedule.meters_pm_schedules__pm_schedule_id,
104
+ pm_events__pm_event_type_id: PM_EVENT_TYPES__DELAY_BY_DAYS,
105
+ pm_events__interval: 30,
106
+ });
107
+ },
108
+ onAddEditWorkOrder = async (metersPmSchedule) => {
109
+ if (!metersPmSchedule.meters_pm_schedules__has_open_work_order) {
110
+ addWorkOrder(null, {
111
+ work_orders__wo_class_id: WO_CLASSES__PM,
112
+ work_orders__equipment: JSON.stringify([{
113
+ id: metersPmSchedule.meters_pm_schedules__meter_id,
114
+ text: metersPmSchedule.equipment__nickname,
115
+ }]),
116
+ work_orders__pm_schedule_id: metersPmSchedule.meters_pm_schedules__pm_schedule_id,
117
+ });
118
+ } else {
119
+ await WorkOrders.loadOneAdditionalEntity(metersPmSchedule.meters_pm_schedules__open_work_order_id);
120
+ const workOrderToEdit = WorkOrders.getById(metersPmSchedule.meters_pm_schedules__open_work_order_id);
121
+ setWorkOrderIsIgnoreNextSelectionChange(true);
122
+ setWorkOrderSelection([workOrderToEdit]);
123
+ editWorkOrder();
124
+ }
125
+ },
126
+ onViewEquipment = async (metersPmSchedule) => {
127
+ const
128
+ Editor = EquipmentEditor,
129
+ id = metersPmSchedule.meters__equipment_id,
130
+ repository = Equipment;
131
+ if (repository.isLoading) {
132
+ await repository.waitUntilDoneLoading();
133
+ }
134
+ let record = repository.getById(id);
135
+ if (!record && repository.loadOneAdditionalEntity) {
136
+ record = await repository.loadOneAdditionalEntity(id);
137
+ }
138
+ if (!record) {
139
+ alert('Equipment record could not be found!');
140
+ return;
141
+ }
142
+
143
+ showModal({
144
+ title: 'Equipment Viewer',
145
+ body: <Editor
146
+ editorType={EDITOR_TYPE__WINDOWED}
147
+ parent={self}
148
+ reference="viewer"
149
+ Repository={repository}
150
+ isEditorViewOnly={true}
151
+ selection={[record]}
152
+ onEditorClose={hideModal}
153
+ className={`
154
+ w-full
155
+ p-0
156
+ `}
157
+ />,
158
+ onCancel: hideModal,
159
+ h: height,
160
+ w: width,
161
+ });
162
+ },
163
+ onShowCalcDebug = async (metersPmSchedule) => {
164
+ showModal({
165
+ body: <PmCalcDebugViewer
166
+ parent={self}
167
+ reference="viewer"
168
+ metersPmSchedule={metersPmSchedule}
169
+ onClose={hideModal}
170
+ className={`
171
+ w-full
172
+ p-0
173
+ `}
174
+ />,
175
+ onCancel: hideModal,
176
+ h: height,
177
+ w: width,
178
+ });
179
+ };
180
+
181
+ useEffect(() => {
182
+ if (!UpcomingPms) {
183
+ return;
184
+ }
185
+ if (!nodeId || !nodeType) {
186
+ return;
187
+ }
188
+
189
+ (async () => {
190
+ let isChanged = false;
191
+ if (!UpcomingPms.hasBaseParam('forUpcomingPms')) {
192
+ UpcomingPms.setBaseParam('forUpcomingPms', true);
193
+ isChanged = true;
194
+ }
195
+ if (nodeId !== null && typeof nodeId !== 'undefined' && UpcomingPms.getBaseParam('nodeId') !== nodeId) {
196
+ UpcomingPms.setBaseParam('nodeId', nodeId);
197
+ isChanged = true;
198
+ }
199
+ if (nodeType !== null && typeof nodeType !== 'undefined' && UpcomingPms.getBaseParam('nodeType') !== nodeType) {
200
+ UpcomingPms.setBaseParam('nodeType', nodeType);
201
+ isChanged = true;
202
+ }
203
+ if (isChanged && UpcomingPms.isLoaded) {
204
+ await UpcomingPms.reload();
205
+ }
206
+ setIsReady(true);
207
+ })();
208
+
209
+ }, [nodeId, nodeType, UpcomingPms]);
210
+
211
+ if (!isReady) {
212
+ return <Loading />;
213
+ }
214
+
215
+ return <VStack className="flex-1 w-full">
216
+ <ScreenHeader title="Upcoming PMs" icon={OilCan} />
217
+ <Grid
218
+ reference="upcomingPmsGrid"
219
+ Repository={UpcomingPms}
220
+ useFilters={true}
221
+ searchAllText={false}
222
+ forceLoadOnRender={true}
223
+ areCellsScrollable={false}
224
+ showClearFiltersButton={false}
225
+ customFilters={[
226
+ {
227
+ id: 'nextPmDue',
228
+ title: 'Show Thru',
229
+ tooltip: 'This is the latest date to display',
230
+ field: 'nextPmDue',
231
+ type: 'Date',
232
+ value: UiGlobals.dates.oneMonthFromNow,
233
+ getRepoFilters: (value) => {
234
+ return [
235
+ {
236
+ name: 'nextPmDue',
237
+ value,
238
+ },
239
+ ];
240
+ },
241
+ },
242
+ {
243
+ id: 'showOverdue',
244
+ title: 'Overdue?',
245
+ tooltip: 'Should we include overdue PMs?',
246
+ field: 'showOverdue',
247
+ type: 'Toggle',
248
+ value: true,
249
+ getRepoFilters: (value) => {
250
+ return [
251
+ {
252
+ name: 'showOverdue',
253
+ value,
254
+ },
255
+ ];
256
+ },
257
+ },
258
+ {
259
+ id: 'showDue',
260
+ title: 'Due?',
261
+ tooltip: 'Should we include due PMs?',
262
+ field: 'showDue',
263
+ type: 'Toggle',
264
+ value: true,
265
+ getRepoFilters: (value) => {
266
+ return [
267
+ {
268
+ name: 'showDue',
269
+ value,
270
+ },
271
+ ];
272
+ },
273
+ },
274
+ {
275
+ id: 'showOk',
276
+ title: 'OK?',
277
+ tooltip: 'Should we include OK PMs?',
278
+ field: 'showOk',
279
+ type: 'Toggle',
280
+ value: false,
281
+ getRepoFilters: (value) => {
282
+ return [
283
+ {
284
+ name: 'showOk',
285
+ value,
286
+ },
287
+ ];
288
+ },
289
+ },
290
+ {
291
+ id: 'showWillCall',
292
+ title: 'Will Call?',
293
+ tooltip: 'Should we include "will call" PMs?',
294
+ field: 'showWillCall',
295
+ type: 'Toggle',
296
+ value: false,
297
+ getRepoFilters: (value) => {
298
+ return [
299
+ {
300
+ name: 'showWillCall',
301
+ value,
302
+ },
303
+ ];
304
+ },
305
+ },
306
+ ]}
307
+ columnsConfig={[
308
+ {
309
+ id: 'bump',
310
+ header: 'Bump',
311
+ w: 70,
312
+ isSortable: false,
313
+ isEditable: false,
314
+ isReorderable: false,
315
+ isResizable: false,
316
+ isHidable: false,
317
+ renderer: (entity, fieldName, cellProps, key) => {
318
+ const className = clsx(
319
+ cellProps.className,
320
+ 'flex',
321
+ 'items-center',
322
+ 'justify-center',
323
+ );
324
+ return <IconButton
325
+ key={key}
326
+ {...cellProps}
327
+ className={className}
328
+ icon={Bump}
329
+ _icon={{
330
+ size: 'xl',
331
+ }}
332
+ onPress={() => onBump(entity)}
333
+ tooltip="Bump"
334
+ />;
335
+ },
336
+ },
337
+ ...(includeWorkOrderButton ? [
338
+ {
339
+ id: 'wo',
340
+ header: '+WO',
341
+ w: 60,
342
+ isSortable: false,
343
+ isEditable: false,
344
+ isReorderable: false,
345
+ isResizable: false,
346
+ isHidable: false,
347
+ renderer: (entity, fieldName, cellProps, key) => {
348
+ const className = clsx(
349
+ cellProps.className,
350
+ 'flex',
351
+ 'items-center',
352
+ 'justify-center'
353
+ );
354
+ return <IconButton
355
+ key={key}
356
+ {...cellProps}
357
+ className={className}
358
+ icon={Clipboard}
359
+ _icon={{
360
+ size: 'xl',
361
+ }}
362
+ onPress={() => onAddEditWorkOrder(entity)}
363
+ tooltip="Create/edit a work order to reset this PM. Only resets when work order is closed."
364
+ />;
365
+ },
366
+ },
367
+ ] : []),
368
+ {
369
+ id: 'calc',
370
+ header: 'Calc',
371
+ w: 60,
372
+ isSortable: false,
373
+ isEditable: false,
374
+ isReorderable: false,
375
+ isResizable: false,
376
+ isHidable: false,
377
+ renderer: (entity, fieldName, cellProps, key) => {
378
+ const className = clsx(
379
+ cellProps.className,
380
+ 'flex',
381
+ 'items-center',
382
+ 'justify-center'
383
+ );
384
+ return <IconButton
385
+ key={key}
386
+ {...cellProps}
387
+ className={className}
388
+ icon={Calculator}
389
+ _icon={{
390
+ size: 'xl',
391
+ }}
392
+ onPress={() => onShowCalcDebug(entity)}
393
+ tooltip="Show how this PM's 'Next PM Due' was calculated"
394
+ />;
395
+ },
396
+ },
397
+ {
398
+ id: 'meter',
399
+ header: 'EQ',
400
+ w: 60,
401
+ isSortable: false,
402
+ isEditable: false,
403
+ isReorderable: false,
404
+ isResizable: false,
405
+ isHidable: false,
406
+ renderer: (entity, fieldName, cellProps, key) => {
407
+ const className = clsx(
408
+ cellProps.className,
409
+ 'flex',
410
+ 'items-center',
411
+ 'justify-center'
412
+ );
413
+ return <IconButton
414
+ key={key}
415
+ {...cellProps}
416
+ className={className}
417
+ icon={EquipmentIcon}
418
+ _icon={{
419
+ size: 'xl',
420
+ }}
421
+ onPress={() => onViewEquipment(entity)}
422
+ tooltip="View Equipment"
423
+ />;
424
+ },
425
+ },
426
+ {
427
+ id: 'meters__nickname',
428
+ header: 'Eq/Meter',
429
+ fieldName: 'meters__nickname',
430
+ isSortable: false,
431
+ isEditable: false,
432
+ isReorderable: false,
433
+ isResizable: true,
434
+ w: 150,
435
+ },
436
+ ...(includeWorkOrderButton ? [
437
+ {
438
+ id: 'meters_pm_schedules__has_open_work_order',
439
+ header: 'WIP?',
440
+ fieldName: 'meters_pm_schedules__has_open_work_order',
441
+ isSortable: true,
442
+ isEditable: true,
443
+ isReorderable: true,
444
+ isResizable: true,
445
+ w: 60,
446
+ },
447
+ ] : []),
448
+ {
449
+ id: 'meters_pm_schedules__next_pm_due',
450
+ header: 'Next PM Due',
451
+ fieldName: 'meters_pm_schedules__next_pm_due',
452
+ isSortable: true,
453
+ isEditable: false,
454
+ isReorderable: false,
455
+ isResizable: true,
456
+ w: 140,
457
+ },
458
+ {
459
+ id: 'pm_statuses__name',
460
+ header: 'PM Status',
461
+ fieldName: 'pm_statuses__name',
462
+ isSortable: true,
463
+ isEditable: false,
464
+ isReorderable: false,
465
+ isResizable: true,
466
+ w: 100,
467
+ },
468
+ {
469
+ id: 'meters_pm_schedules__latest_pm_date',
470
+ header: 'Last PM',
471
+ fieldName: 'meters_pm_schedules__latest_pm_date',
472
+ isSortable: true,
473
+ isEditable: false,
474
+ isReorderable: false,
475
+ isResizable: true,
476
+ w: 140,
477
+ },
478
+ {
479
+ id: 'pm_schedules__name',
480
+ header: 'Pm Schedule',
481
+ fieldName: 'pm_schedules__name',
482
+ isSortable: false,
483
+ isEditable: false,
484
+ isReorderable: false,
485
+ isResizable: true,
486
+ w: 250,
487
+ },
488
+ {
489
+ id: 'meters_pm_schedules__display_path',
490
+ header: 'Path',
491
+ fieldName: 'meters_pm_schedules__display_path',
492
+ isSortable: true,
493
+ isEditable: false,
494
+ isReorderable: false,
495
+ isResizable: true,
496
+ w: 300,
497
+ },
498
+ ]}
499
+ getRowProps={getRowProps}
500
+ />
501
+ </VStack>;
502
+ }
503
+
504
+ function reloadUpcomingPms() {
505
+ oneHatData.getRepository('UpcomingPms').reload();
506
+ }
507
+
508
+ function withBumper(WrappedComponent) {
509
+ const Component = withAlert(withData(withSelection(withWindowedEditor(WrappedComponent))));
510
+ return (props) => {
511
+ const {
512
+ onAdd,
513
+ onEdit,
514
+ setSelection,
515
+ setIsIgnoreNextSelectionChange,
516
+ Repository,
517
+ ...propsToPass
518
+ } = props;
519
+
520
+ return <Component
521
+ {...propsToPass}
522
+ reference="bumper"
523
+ Editor={BumpPmsEditorWindow}
524
+ model="PmEvents"
525
+ onAdd={reloadUpcomingPms}
526
+ alreadyHasWithEditor={false}
527
+ alreadyHasWithData={false}
528
+ alreadyHasWithSelection={false}
529
+ addWorkOrder={onAdd}
530
+ editWorkOrder={onEdit}
531
+ setWorkOrderSelection={setSelection}
532
+ setWorkOrderIsIgnoreNextSelectionChange={setIsIgnoreNextSelectionChange}
533
+ />;
534
+ };
535
+ }
536
+
537
+ function withWorkOrdersAdder(WrappedComponent) {
538
+ const Component = withAlert(withData(withSelection(withWindowedEditor(WrappedComponent))));
539
+ return (props) => {
540
+ const {
541
+ BumpWorkOrdersEditorWindow,
542
+ } = props;
543
+
544
+ if (!BumpWorkOrdersEditorWindow) {
545
+ return <WrappedComponent {...props} />;
546
+ }
547
+
548
+ return <Component
549
+ {...props}
550
+ reference="workOrdersAdder"
551
+ Editor={BumpWorkOrdersEditorWindow}
552
+ model="WorkOrders"
553
+ onAdd={reloadUpcomingPms}
554
+ onSave={reloadUpcomingPms}
555
+ onDelete={reloadUpcomingPms}
556
+ />;
557
+ };
558
+ }
559
+
560
+ function withReference(WrappedComponent) {
561
+ return (props) => {
562
+ return <WrappedComponent
563
+ reference="UpcomingPmsGrid"
564
+ {...props}
565
+ />;
566
+ };
567
+ }
568
+
569
+ export default withReference(withComponent(withWorkOrdersAdder(withBumper(UpcomingPmsGrid))));
@@ -0,0 +1,11 @@
1
+ import {
2
+ Text,
3
+ } from '@project-components/Gluestack';
4
+ import clsx from 'clsx';
5
+ import CenterBox from '../../../Layout/CenterBox';
6
+
7
+ export default function MakeTreeSelection(props) {
8
+ return <CenterBox className="TreeSpecific-CenterBox bg-grey-100">
9
+ <Text className="text-center">Please make a selection on the tree</Text>
10
+ </CenterBox>;
11
+ }
@@ -0,0 +1,30 @@
1
+ import { Children, cloneElement } from 'react';
2
+ import MakeTreeSelection from './MakeTreeSelection';
3
+ import { useSelector } from 'react-redux';
4
+ import {
5
+ selectTreeSelection,
6
+ } from '@src/Models/Slices/AppSlice';
7
+ import _ from 'lodash';
8
+
9
+ export default function TreeSpecific(props) {
10
+
11
+ const {
12
+ children,
13
+ ...propsToPass
14
+ } = props,
15
+ treeSelection = useSelector(selectTreeSelection),
16
+ hasTreeSelection = !_.isEmpty(treeSelection);
17
+
18
+ if (!hasTreeSelection) {
19
+ return <MakeTreeSelection {...props} />;
20
+ }
21
+
22
+ // clone children and pass down props
23
+ return Children.map(children, (child) => {
24
+ if (child && typeof child === 'object' && child.type) {
25
+ // valid React element
26
+ return cloneElement(child, propsToPass);
27
+ }
28
+ return child;
29
+ });
30
+ }
@@ -0,0 +1,104 @@
1
+ import { useState } from 'react';
2
+ import {
3
+ Text,
4
+ VStack,
5
+ } from '@project-components/Gluestack';
6
+ import oneHatData from '@onehat/data';
7
+ import {
8
+ ASYNC_OPERATION_MODES__INIT,
9
+ ASYNC_OPERATION_MODES__START,
10
+ ASYNC_OPERATION_MODES__PROCESSING,
11
+ ASYNC_OPERATION_MODES__RESULTS,
12
+ } from '../../../Constants/Progress.js';
13
+ import inArray from '../../../Functions/inArray.js';
14
+ import AsyncOperation from '../../Layout/AsyncOperation';
15
+ import testProps from '../../../Functions/testProps.js';
16
+ import Button from '../../Buttons/Button.js';
17
+ import Toolbar from '../../Toolbar/Toolbar.js';
18
+ import Xmark from '../../Icons/Xmark.js';
19
+ import * as yup from 'yup';
20
+
21
+
22
+ // This is the body of a Modal
23
+
24
+
25
+ export default function BulkAssignTechnician(props) {
26
+ const {
27
+ selection,
28
+ hideModal,
29
+ } = props,
30
+ [mode, setMode] = useState(ASYNC_OPERATION_MODES__INIT),
31
+ FleetsRepository = oneHatData.getRepository('Fleets'),
32
+ EquipmentRepository = oneHatData.getRepository('Equipment');
33
+
34
+ if (!selection[0]?.repository) {
35
+ return;
36
+ }
37
+
38
+ // construct assignTo (e.g. 'all equipment in Peoria', 'all equipment in 3 fleets', 'Unit 12345' etc)
39
+ let nodeType = selection[0]?.nodeType || 'Equipment', // LitesTree has nodeType, EquipmentGrid does not
40
+ assignTo = '',
41
+ hiddenFieldName = 'equipment_id';
42
+ if (nodeType === 'Fleets') {
43
+ assignTo = 'all equipment in ';
44
+ hiddenFieldName = 'fleet_id';
45
+ }
46
+ if (selection.length === 1) {
47
+ assignTo += selection[0].displayValue;
48
+ } else {
49
+ assignTo += selection.length + ' ' + nodeType.charAt(0).toLowerCase() + nodeType.slice(1); // make first letter lower case
50
+ }
51
+
52
+ return <VStack className="h-full">
53
+ <AsyncOperation
54
+ Repository={selection[0]?.nodeType === 'Fleets' ? FleetsRepository : EquipmentRepository}
55
+ title="Bulk Assign Technician"
56
+ isCollapsible={false}
57
+ reference="BulkAssignTechnician"
58
+ process="BulkAssignTechnician"
59
+ getProgressUpdates={true}
60
+ getInitialProgress={false}
61
+ updateInterval={1000}
62
+ onChangeMode={setMode}
63
+ _form={{
64
+ items: [
65
+ {
66
+ type: 'DisplayField',
67
+ text: `You are about to assign a technician to ${assignTo}.`,
68
+ },
69
+ {
70
+ type: 'PmTechniciansCombo',
71
+ name: 'user_id',
72
+ label: 'Technician',
73
+ className: 'mb-1 mt-4',
74
+ tooltip: 'Which technician to assign.',
75
+ },
76
+ {
77
+ type: 'Hidden',
78
+ name: hiddenFieldName,
79
+ },
80
+ ],
81
+ validator: yup.object({
82
+ user_id: yup.number().integer().required(),
83
+ }),
84
+ startingValues: {
85
+ user_id: null,
86
+ [hiddenFieldName]: selection.map(item => item.actualId).join(','),
87
+ },
88
+ }}
89
+ />
90
+ <Toolbar>
91
+ <Button
92
+ {...testProps('cancelBtn')}
93
+ key="cancelBtn"
94
+ variant="outline"
95
+ icon={Xmark}
96
+ onPress={() => hideModal()}
97
+ className="text-white"
98
+ text={inArray(mode, [ASYNC_OPERATION_MODES__INIT, ASYNC_OPERATION_MODES__START]) ? 'Cancel' : (mode === ASYNC_OPERATION_MODES__PROCESSING ? 'Wait' : 'Close')}
99
+ isDisabled={mode === ASYNC_OPERATION_MODES__PROCESSING}
100
+ />
101
+ </Toolbar>
102
+ </VStack>;
103
+
104
+ }