@onehat/ui 0.4.103 → 0.4.105
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/.github/copilot-instructions.md.bak.20260307094051 +65 -0
- package/package.json +1 -1
- package/src/Components/Form/Field/Combo/Combo.js +8 -2
- package/src/Components/Form/Form.js +12 -8
- package/src/Components/Grid/Grid.js +4 -0
- package/src/Components/Grid/GridRow.js +5 -1
- package/src/Components/Hoc/Secondary/withSecondaryEditor.js +18 -1
- package/src/Components/Hoc/withEditor.js +237 -20
- package/src/Components/Hoc/withPdfButtons.js +3 -0
- package/src/Components/Hoc/withPresetButtons.js +2 -0
- package/src/Components/Panel/Panel.js +37 -9
- package/src/Components/Pms/Editor/PmEventsEditor.js +3 -3
- package/src/Components/Pms/Screens/PmsManager.js +1 -1
- package/src/Components/Screens/Manager.js +2 -1
- package/src/Components/Toolbar/PaginationToolbar.js +5 -3
- package/src/Components/Viewer/Viewer.js +7 -4
- package/src/Constants/PmSchedules.js +1 -0
- package/src/Contexts/EditorModeContext.js +5 -0
- package/src/Functions/buildAdditionalButtons.js +5 -0
- package/src/PlatformImports/Web/Attachments.js +26 -0
- package/src/Constants/EditorModes.js +0 -2
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Copilot Instructions
|
|
2
|
+
|
|
3
|
+
## General Principles
|
|
4
|
+
- Be direct, concise, and critical. Do not apologize, agree blindly, be sycophantic, or use flattery.
|
|
5
|
+
- If a request is inefficient or flawed, point it out and suggest a better, more secure, or more idiomatic approach.
|
|
6
|
+
- Use "Uncle Bob's Clean Code" principles as a baseline for code quality.
|
|
7
|
+
- Prioritize clarity over cleverness.
|
|
8
|
+
- Avoid unnecessary abstractions.
|
|
9
|
+
- Prefer explicit code over magic.
|
|
10
|
+
- Follow the project's existing coding style and conventions.
|
|
11
|
+
- Do not introduce new dependencies unless absolutely necessary.
|
|
12
|
+
- Always consider security implications and best practices.
|
|
13
|
+
- Aim for full test coverage of new code, and suggest tests for existing code when appropriate.
|
|
14
|
+
|
|
15
|
+
<!-- ## Coding Standards
|
|
16
|
+
|
|
17
|
+
### Naming
|
|
18
|
+
- Use descriptive variable and function names.
|
|
19
|
+
- Do not abbreviate unless universally understood.
|
|
20
|
+
- Use camelCase for JavaScript variables and functions.
|
|
21
|
+
- Use PascalCase for React components.
|
|
22
|
+
- Use snake_case for database columns.
|
|
23
|
+
|
|
24
|
+
### Comments
|
|
25
|
+
- Do not write obvious comments.
|
|
26
|
+
- Only comment non-obvious business logic.
|
|
27
|
+
- Prefer self-documenting code.
|
|
28
|
+
|
|
29
|
+
## Error Handling
|
|
30
|
+
- Always handle errors explicitly.
|
|
31
|
+
- Do not swallow exceptions.
|
|
32
|
+
- Return meaningful error messages.
|
|
33
|
+
|
|
34
|
+
## Security
|
|
35
|
+
- Never expose secrets or API keys.
|
|
36
|
+
- Validate and sanitize all user input.
|
|
37
|
+
- Use prepared statements for database queries.
|
|
38
|
+
|
|
39
|
+
## Project-Specific Notes
|
|
40
|
+
|
|
41
|
+
### Backend (PHP / CakePHP)
|
|
42
|
+
- Follow PSR-12 formatting.
|
|
43
|
+
- Use dependency injection where appropriate.
|
|
44
|
+
- Do not use deprecated framework methods.
|
|
45
|
+
- Prefer modern APIs over legacy helpers.
|
|
46
|
+
|
|
47
|
+
### Frontend (React / Expo)
|
|
48
|
+
- Use functional components only.
|
|
49
|
+
- Prefer hooks over class components.
|
|
50
|
+
- Avoid inline styles unless necessary.
|
|
51
|
+
- Keep components under 200 lines.
|
|
52
|
+
|
|
53
|
+
## Testing
|
|
54
|
+
- Suggest unit tests for new logic.
|
|
55
|
+
- Mock external dependencies.
|
|
56
|
+
- Keep tests deterministic.
|
|
57
|
+
|
|
58
|
+
## Performance
|
|
59
|
+
- Avoid unnecessary loops.
|
|
60
|
+
- Do not perform database queries inside loops.
|
|
61
|
+
- Prefer memoization when appropriate.
|
|
62
|
+
|
|
63
|
+
## Output Expectations
|
|
64
|
+
- Produce production-ready code.
|
|
65
|
+
- Avoid TODO comments unless necessary. -->
|
package/package.json
CHANGED
|
@@ -90,6 +90,7 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
90
90
|
isInTag = false,
|
|
91
91
|
minimizeForRow = false,
|
|
92
92
|
reloadOnTrigger = false,
|
|
93
|
+
loadAfterRender = false,
|
|
93
94
|
searchHasInitialPercent = false,
|
|
94
95
|
menuHeight,
|
|
95
96
|
placeholder,
|
|
@@ -585,11 +586,16 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
585
586
|
};
|
|
586
587
|
|
|
587
588
|
useEffect(() => {
|
|
588
|
-
// on render, focus the input
|
|
589
589
|
if (!isRendered) {
|
|
590
590
|
return () => {};
|
|
591
591
|
}
|
|
592
|
+
|
|
593
|
+
if (loadAfterRender) {
|
|
594
|
+
Repository?.reload();
|
|
595
|
+
}
|
|
596
|
+
|
|
592
597
|
if (autoFocus && !inputRef.current.isFocused()) {
|
|
598
|
+
// on render, focus the input
|
|
593
599
|
inputRef.current.focus();
|
|
594
600
|
}
|
|
595
601
|
|
|
@@ -599,7 +605,7 @@ export const ComboComponent = forwardRef((props, ref) => {
|
|
|
599
605
|
}
|
|
600
606
|
};
|
|
601
607
|
|
|
602
|
-
}, [isRendered]);
|
|
608
|
+
}, [isRendered, loadAfterRender, Repository]);
|
|
603
609
|
|
|
604
610
|
useEffect(() => {
|
|
605
611
|
(async () => {
|
|
@@ -139,6 +139,7 @@ function Form(props) {
|
|
|
139
139
|
onDelete,
|
|
140
140
|
editorStateRef,
|
|
141
141
|
disableView,
|
|
142
|
+
isEditorModeControlledByParent = false,
|
|
142
143
|
|
|
143
144
|
// parent container
|
|
144
145
|
selectorId,
|
|
@@ -253,8 +254,9 @@ function Form(props) {
|
|
|
253
254
|
'border-r-grey-200',
|
|
254
255
|
'px-1',
|
|
255
256
|
styles.INLINE_EDITOR_MIN_WIDTH,
|
|
256
|
-
)
|
|
257
|
-
|
|
257
|
+
),
|
|
258
|
+
validColumnsConfig = _.filter(columnsConfig, (config) => !!config); // filter out any null/undefined configs
|
|
259
|
+
_.each(validColumnsConfig, (config, ix) => {
|
|
258
260
|
let {
|
|
259
261
|
fieldName,
|
|
260
262
|
isEditable = false,
|
|
@@ -273,7 +275,7 @@ function Form(props) {
|
|
|
273
275
|
type,
|
|
274
276
|
editorTypeProps = {},
|
|
275
277
|
viewerTypeProps = {};
|
|
276
|
-
|
|
278
|
+
|
|
277
279
|
if (isHidden) {
|
|
278
280
|
return;
|
|
279
281
|
}
|
|
@@ -965,9 +967,11 @@ function Form(props) {
|
|
|
965
967
|
/>;
|
|
966
968
|
},
|
|
967
969
|
buildAncillary = () => {
|
|
968
|
-
const
|
|
970
|
+
const
|
|
971
|
+
validAncillaryItems = _.filter(ancillaryItems, (item) => !!item), // filter out any null/undefined items
|
|
972
|
+
components = [];
|
|
969
973
|
setAncillaryButtons([]);
|
|
970
|
-
if (
|
|
974
|
+
if (validAncillaryItems.length) {
|
|
971
975
|
|
|
972
976
|
// add the "scroll to top" button
|
|
973
977
|
getAncillaryButtons().push({
|
|
@@ -978,7 +982,7 @@ function Form(props) {
|
|
|
978
982
|
tooltip: 'Scroll to top',
|
|
979
983
|
});
|
|
980
984
|
|
|
981
|
-
_.each(
|
|
985
|
+
_.each(validAncillaryItems, (item, ix) => {
|
|
982
986
|
let {
|
|
983
987
|
type,
|
|
984
988
|
title = null,
|
|
@@ -1259,7 +1263,7 @@ function Form(props) {
|
|
|
1259
1263
|
|
|
1260
1264
|
if (inArray(editorType, [EDITOR_TYPE__SIDE, EDITOR_TYPE__SMART, EDITOR_TYPE__WINDOWED]) &&
|
|
1261
1265
|
isSingle && getEditorMode() === EDITOR_MODE__EDIT &&
|
|
1262
|
-
(onBack || onViewMode)) {
|
|
1266
|
+
(onBack || onViewMode || isEditorModeControlledByParent)) {
|
|
1263
1267
|
modeHeader = <Toolbar>
|
|
1264
1268
|
<HStack className="flex-1 items-center">
|
|
1265
1269
|
{onBack &&
|
|
@@ -1277,7 +1281,7 @@ function Form(props) {
|
|
|
1277
1281
|
)}
|
|
1278
1282
|
text="Back"
|
|
1279
1283
|
/>}
|
|
1280
|
-
<Text className="text-[20px] ml-1 text-grey-500">Edit Mode</Text>
|
|
1284
|
+
<Text className="text-[20px] ml-1 text-grey-500">{isEditorModeControlledByParent ? 'Edit Mode (Inherited)' : 'Edit Mode'}</Text>
|
|
1281
1285
|
</HStack>
|
|
1282
1286
|
{onViewMode && !disableView && (!canUser || canUser(VIEW)) &&
|
|
1283
1287
|
<Button
|
|
@@ -589,6 +589,8 @@ function GridComponent(props) {
|
|
|
589
589
|
'flex-row',
|
|
590
590
|
'grow',
|
|
591
591
|
'max-h-[80px]',
|
|
592
|
+
'focus:outline-none', // hide the focus outline
|
|
593
|
+
'focus-visible:outline-none',
|
|
592
594
|
)}
|
|
593
595
|
>
|
|
594
596
|
{({
|
|
@@ -1914,6 +1916,8 @@ function GridComponent(props) {
|
|
|
1914
1916
|
'w-full',
|
|
1915
1917
|
'border',
|
|
1916
1918
|
'border-grey-300',
|
|
1919
|
+
'focus:outline-none', // hide the focus outline
|
|
1920
|
+
'focus-visible:outline-none',
|
|
1917
1921
|
);
|
|
1918
1922
|
if (props.className) {
|
|
1919
1923
|
className += ' ' + props.className;
|
|
@@ -490,7 +490,11 @@ const GridRow = forwardRef((props, ref) => {
|
|
|
490
490
|
}}
|
|
491
491
|
>{rowContents}</HStackNative>;
|
|
492
492
|
if (rowProps.tooltip) {
|
|
493
|
-
row = <Tooltip
|
|
493
|
+
row = <Tooltip
|
|
494
|
+
label={rowProps.tooltip}
|
|
495
|
+
placement="bottom left"
|
|
496
|
+
triggerClassName={rowClassName}
|
|
497
|
+
>{row}</Tooltip>;
|
|
494
498
|
}
|
|
495
499
|
return row;
|
|
496
500
|
}, [
|
|
@@ -47,6 +47,7 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
47
47
|
},
|
|
48
48
|
secondaryEditorType,
|
|
49
49
|
secondaryOnAdd,
|
|
50
|
+
secondaryOnBeforeAdd,
|
|
50
51
|
secondaryOnChange, // any kind of crud change
|
|
51
52
|
secondaryOnBeforeDelete,
|
|
52
53
|
secondaryOnDelete,
|
|
@@ -201,10 +202,26 @@ export default function withSecondaryEditor(WrappedComponent, isTree = false) {
|
|
|
201
202
|
}
|
|
202
203
|
|
|
203
204
|
if (getListeners().onBeforeAdd) {
|
|
204
|
-
|
|
205
|
+
// This listener is set by child components using setWithEditListeners()
|
|
206
|
+
const listenerResult = await getListeners().onBeforeAdd(addValues);
|
|
207
|
+
if (listenerResult === false) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (listenerResult) {
|
|
211
|
+
// allow the listener to override the addValues by returning an object
|
|
212
|
+
addValues = listenerResult;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (secondaryOnBeforeAdd) {
|
|
216
|
+
// This listener is set by parent components using a prop
|
|
217
|
+
const listenerResult = await secondaryOnBeforeAdd(addValues);
|
|
205
218
|
if (listenerResult === false) {
|
|
206
219
|
return;
|
|
207
220
|
}
|
|
221
|
+
if (listenerResult) {
|
|
222
|
+
// allow the listener to override the addValues by returning an object
|
|
223
|
+
addValues = listenerResult;
|
|
224
|
+
}
|
|
208
225
|
}
|
|
209
226
|
|
|
210
227
|
if (isTree) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { forwardRef, useEffect, useState, useRef, } from 'react';
|
|
1
|
+
import { forwardRef, useContext, useEffect, useState, useRef, } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
ADD,
|
|
4
4
|
EDIT,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from '../../Constants/Editor.js';
|
|
16
16
|
import useForceUpdate from '../../Hooks/useForceUpdate.js'
|
|
17
17
|
import Button from '../Buttons/Button.js';
|
|
18
|
+
import EditorModeContext from '../../Contexts/EditorModeContext.js';
|
|
18
19
|
import UiGlobals from '../../UiGlobals.js';
|
|
19
20
|
import _ from 'lodash';
|
|
20
21
|
|
|
@@ -47,6 +48,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
47
48
|
},
|
|
48
49
|
editorType,
|
|
49
50
|
onAdd,
|
|
51
|
+
onBeforeAdd,
|
|
50
52
|
onChange, // any kind of crud change
|
|
51
53
|
onBeforeDelete,
|
|
52
54
|
onDelete,
|
|
@@ -57,6 +59,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
57
59
|
defaultValues,
|
|
58
60
|
initialEditorMode = EDITOR_MODE__VIEW,
|
|
59
61
|
stayInEditModeOnSelectionChange = false,
|
|
62
|
+
inheritParentEditorMode = true,
|
|
60
63
|
|
|
61
64
|
// withComponent
|
|
62
65
|
self,
|
|
@@ -83,6 +86,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
83
86
|
confirm,
|
|
84
87
|
hideAlert,
|
|
85
88
|
} = props,
|
|
89
|
+
parentEditorModeContext = useContext(EditorModeContext),
|
|
86
90
|
forceUpdate = useForceUpdate(),
|
|
87
91
|
listeners = useRef({}),
|
|
88
92
|
editorStateRef = useRef(),
|
|
@@ -169,13 +173,85 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
169
173
|
forceUpdate();
|
|
170
174
|
}
|
|
171
175
|
},
|
|
176
|
+
getParentEditorMode = () => {
|
|
177
|
+
return parentEditorModeContext?.effectiveEditorMode || null;
|
|
178
|
+
},
|
|
179
|
+
getInheritedEditorMode = () => {
|
|
180
|
+
if (!inheritParentEditorMode) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
const parentMode = getParentEditorMode();
|
|
184
|
+
if (parentMode === EDITOR_MODE__ADD) {
|
|
185
|
+
return EDITOR_MODE__EDIT;
|
|
186
|
+
}
|
|
187
|
+
if (parentMode === EDITOR_MODE__EDIT || parentMode === EDITOR_MODE__VIEW) {
|
|
188
|
+
return parentMode;
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
},
|
|
192
|
+
getIsParentSaveLocked = () => {
|
|
193
|
+
return !!parentEditorModeContext?.isAnyAncestorUnsaved;
|
|
194
|
+
},
|
|
195
|
+
getIsCurrentSelectionUnsaved = () => {
|
|
196
|
+
const selection = getSelection();
|
|
197
|
+
if (!selection || selection.length !== 1) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
const record = selection[0];
|
|
201
|
+
if (!record || record.isDestroyed) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
return !!(record.isPhantom || record.isRemotePhantom);
|
|
205
|
+
},
|
|
206
|
+
getIsEditorDisabledByParent = () => {
|
|
207
|
+
return getIsParentSaveLocked();
|
|
208
|
+
},
|
|
209
|
+
getIsEditorModeControlledByParent = () => {
|
|
210
|
+
return !!getInheritedEditorMode();
|
|
211
|
+
},
|
|
172
212
|
getNewEntityDisplayValue = () => {
|
|
173
213
|
return newEntityDisplayValueRef.current;
|
|
174
214
|
},
|
|
175
215
|
setNewEntityDisplayValue = (val) => {
|
|
176
216
|
newEntityDisplayValueRef.current = val;
|
|
177
217
|
},
|
|
218
|
+
showViewFallback = async () => {
|
|
219
|
+
// helper for doEdit
|
|
220
|
+
// If the editor is forced into EDIT mode by parent inheritance,
|
|
221
|
+
// but the child editor cannot actually be in edit mode due to permissions or configuration,
|
|
222
|
+
// switch the mode to VIEW.
|
|
223
|
+
|
|
224
|
+
if (!userCanView) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (canUser && !canUser(VIEW)) {
|
|
228
|
+
showPermissionsError(VIEW);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (canProceedWithCrud && !canProceedWithCrud()) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (editorType === EDITOR_TYPE__INLINE) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const selection = getSelection();
|
|
238
|
+
if (selection.length !== 1) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
setIsEditorViewOnly(true);
|
|
242
|
+
setEditorMode(EDITOR_MODE__VIEW);
|
|
243
|
+
setIsEditorShown(true);
|
|
244
|
+
if (getListeners().onAfterView) {
|
|
245
|
+
await getListeners().onAfterView();
|
|
246
|
+
}
|
|
247
|
+
},
|
|
178
248
|
doAdd = async (e, values) => {
|
|
249
|
+
if (getIsEditorDisabledByParent()) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (getInheritedEditorMode() === EDITOR_MODE__VIEW) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
179
255
|
if (canUser && !canUser(ADD)) {
|
|
180
256
|
showPermissionsError(ADD);
|
|
181
257
|
return;
|
|
@@ -218,10 +294,26 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
218
294
|
}
|
|
219
295
|
|
|
220
296
|
if (getListeners().onBeforeAdd) {
|
|
221
|
-
|
|
297
|
+
// This listener is set by child components using setWithEditListeners()
|
|
298
|
+
const listenerResult = await getListeners().onBeforeAdd(addValues);
|
|
299
|
+
if (listenerResult === false) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (listenerResult) {
|
|
303
|
+
// allow the listener to override the addValues by returning an object
|
|
304
|
+
addValues = listenerResult;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (onBeforeAdd) {
|
|
308
|
+
// This listener is set by parent components using a prop
|
|
309
|
+
const listenerResult = await onBeforeAdd(addValues);
|
|
222
310
|
if (listenerResult === false) {
|
|
223
311
|
return;
|
|
224
312
|
}
|
|
313
|
+
if (listenerResult) {
|
|
314
|
+
// allow the listener to override the addValues by returning an object
|
|
315
|
+
addValues = listenerResult;
|
|
316
|
+
}
|
|
225
317
|
}
|
|
226
318
|
|
|
227
319
|
if (isTree) {
|
|
@@ -278,7 +370,23 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
278
370
|
setIsEditorShown(true);
|
|
279
371
|
},
|
|
280
372
|
doEdit = async () => {
|
|
373
|
+
if (getIsEditorDisabledByParent()) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const inheritedEditorMode = getInheritedEditorMode();
|
|
377
|
+
if (inheritedEditorMode === EDITOR_MODE__VIEW) {
|
|
378
|
+
await doView(false);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (inheritedEditorMode === EDITOR_MODE__EDIT && (!userCanEdit || disableEdit || canEditorViewOnly)) {
|
|
382
|
+
await showViewFallback();
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
281
385
|
if (canUser && !canUser(EDIT)) {
|
|
386
|
+
if (inheritedEditorMode === EDITOR_MODE__EDIT) {
|
|
387
|
+
await showViewFallback();
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
282
390
|
showPermissionsError(EDIT);
|
|
283
391
|
return;
|
|
284
392
|
}
|
|
@@ -300,6 +408,12 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
300
408
|
setIsEditorShown(true);
|
|
301
409
|
},
|
|
302
410
|
doDelete = async (args) => {
|
|
411
|
+
if (getIsEditorDisabledByParent()) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (getInheritedEditorMode() === EDITOR_MODE__VIEW) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
303
417
|
if (canUser && !canUser(DELETE)) {
|
|
304
418
|
showPermissionsError(DELETE);
|
|
305
419
|
return;
|
|
@@ -413,6 +527,17 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
413
527
|
}
|
|
414
528
|
},
|
|
415
529
|
doView = async (allowEditing = false) => {
|
|
530
|
+
if (getIsEditorDisabledByParent()) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const inheritedEditorMode = getInheritedEditorMode();
|
|
534
|
+
if (inheritedEditorMode === EDITOR_MODE__EDIT) {
|
|
535
|
+
await doEdit();
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (inheritedEditorMode === EDITOR_MODE__VIEW) {
|
|
539
|
+
allowEditing = false;
|
|
540
|
+
}
|
|
416
541
|
if (!userCanView) {
|
|
417
542
|
return;
|
|
418
543
|
}
|
|
@@ -443,6 +568,12 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
443
568
|
}
|
|
444
569
|
},
|
|
445
570
|
doDuplicate = async () => {
|
|
571
|
+
if (getIsEditorDisabledByParent()) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
if (getInheritedEditorMode() === EDITOR_MODE__VIEW) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
446
577
|
if (!userCanEdit || disableDuplicate) {
|
|
447
578
|
return;
|
|
448
579
|
}
|
|
@@ -516,6 +647,9 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
516
647
|
}
|
|
517
648
|
},
|
|
518
649
|
doEditorSave = async (data, e) => {
|
|
650
|
+
if (getIsEditorDisabledByParent()) {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
519
653
|
let mode = getEditorMode() === EDITOR_MODE__ADD ? ADD : EDIT;
|
|
520
654
|
if (canUser && !canUser(mode)) {
|
|
521
655
|
showPermissionsError(mode);
|
|
@@ -634,6 +768,12 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
634
768
|
setIsEditorShown(false);
|
|
635
769
|
},
|
|
636
770
|
doEditorDelete = async () => {
|
|
771
|
+
if (getIsEditorDisabledByParent()) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
if (getInheritedEditorMode() === EDITOR_MODE__VIEW) {
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
637
777
|
if (canUser && !canUser(DELETE)) {
|
|
638
778
|
showPermissionsError(DELETE);
|
|
639
779
|
return;
|
|
@@ -645,6 +785,46 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
645
785
|
});
|
|
646
786
|
},
|
|
647
787
|
calculateEditorMode = () => {
|
|
788
|
+
// Calculate the editor's effective mode based on parent inheritance, permissions, and local selection state.
|
|
789
|
+
// Priority order:
|
|
790
|
+
// 1. If parent is save-locked (unsaved ancestor), force VIEW mode
|
|
791
|
+
// 2. If parent forces VIEW mode via inheritance, return VIEW (child cannot edit if parent is view-only)
|
|
792
|
+
// 3. If parent forces EDIT mode via inheritance, check child permissions:
|
|
793
|
+
// a. If parent disabled, child disabled, or child cannot edit, return VIEW
|
|
794
|
+
// b. If single phantom record, return ADD (new record being created)
|
|
795
|
+
// c. Otherwise return EDIT or VIEW based on selection count
|
|
796
|
+
// 4. Fall back to local selection heuristics (multiple→EDIT, single→VIEW, stays in previous mode if configured)
|
|
797
|
+
const
|
|
798
|
+
selection = getSelection(),
|
|
799
|
+
inheritedEditorMode = getInheritedEditorMode();
|
|
800
|
+
|
|
801
|
+
if (getIsEditorDisabledByParent()) {
|
|
802
|
+
return EDITOR_MODE__VIEW;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
if (inheritedEditorMode === EDITOR_MODE__VIEW) {
|
|
806
|
+
return EDITOR_MODE__VIEW;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
if (inheritedEditorMode === EDITOR_MODE__EDIT) {
|
|
810
|
+
if (!getCanEditorBeInEditMode()) {
|
|
811
|
+
return EDITOR_MODE__VIEW;
|
|
812
|
+
}
|
|
813
|
+
if (canEditorViewOnly || !userCanEdit || disableEdit) {
|
|
814
|
+
return EDITOR_MODE__VIEW;
|
|
815
|
+
}
|
|
816
|
+
if (canUser && !canUser(EDIT)) {
|
|
817
|
+
return EDITOR_MODE__VIEW;
|
|
818
|
+
}
|
|
819
|
+
if (canRecordBeEdited && canRecordBeEdited(selection) === false) {
|
|
820
|
+
return EDITOR_MODE__VIEW;
|
|
821
|
+
}
|
|
822
|
+
if (selection.length === 1 && !selection[0].isDestroyed && (selection[0].isPhantom || selection[0].isRemotePhantom) && !disableAdd) {
|
|
823
|
+
return EDITOR_MODE__ADD;
|
|
824
|
+
}
|
|
825
|
+
return selection.length ? EDITOR_MODE__EDIT : EDITOR_MODE__VIEW;
|
|
826
|
+
}
|
|
827
|
+
|
|
648
828
|
if (!getCanEditorBeInEditMode()) { // this is a result of canRecordBeEdited returning false
|
|
649
829
|
return EDITOR_MODE__VIEW;
|
|
650
830
|
}
|
|
@@ -660,7 +840,6 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
660
840
|
}
|
|
661
841
|
|
|
662
842
|
// calculateEditorMode gets called only on selection changes
|
|
663
|
-
const selection = getSelection();
|
|
664
843
|
let mode;
|
|
665
844
|
if (editorType === EDITOR_TYPE__SIDE && !_.isNil(UiGlobals.isSideEditorAlwaysEditMode) && UiGlobals.isSideEditorAlwaysEditMode) {
|
|
666
845
|
// special case: side editor is always edit mode
|
|
@@ -688,6 +867,9 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
688
867
|
return mode;
|
|
689
868
|
},
|
|
690
869
|
setEditMode = () => {
|
|
870
|
+
if (getIsEditorDisabledByParent() || getIsEditorModeControlledByParent()) {
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
691
873
|
if (canUser && !canUser(EDIT)) {
|
|
692
874
|
showPermissionsError(EDIT);
|
|
693
875
|
return;
|
|
@@ -696,6 +878,9 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
696
878
|
setEditorMode(EDITOR_MODE__EDIT);
|
|
697
879
|
},
|
|
698
880
|
setViewMode = () => {
|
|
881
|
+
if (getIsEditorDisabledByParent() || getIsEditorModeControlledByParent()) {
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
699
884
|
if (canUser && !canUser(VIEW)) {
|
|
700
885
|
showPermissionsError(VIEW);
|
|
701
886
|
return;
|
|
@@ -712,10 +897,20 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
712
897
|
}
|
|
713
898
|
};
|
|
714
899
|
|
|
900
|
+
const
|
|
901
|
+
inheritedEditorMode = getInheritedEditorMode(),
|
|
902
|
+
isEditorDisabledByParent = getIsEditorDisabledByParent(),
|
|
903
|
+
isEditorModeControlledByParent = getIsEditorModeControlledByParent(),
|
|
904
|
+
isCurrentSelectionUnsaved = getIsCurrentSelectionUnsaved(),
|
|
905
|
+
isAnyAncestorUnsaved = getIsParentSaveLocked() || isCurrentSelectionUnsaved,
|
|
906
|
+
isCrudBlockedByInheritedView = inheritedEditorMode === EDITOR_MODE__VIEW;
|
|
907
|
+
|
|
715
908
|
useEffect(() => {
|
|
716
909
|
|
|
717
910
|
if (editorType === EDITOR_TYPE__SIDE) {
|
|
718
|
-
if (
|
|
911
|
+
if (isEditorDisabledByParent) {
|
|
912
|
+
setIsEditorShown(false);
|
|
913
|
+
} else if (selection?.length) { // || isAdding
|
|
719
914
|
// there is a selection, so show the editor
|
|
720
915
|
setIsEditorShown(true);
|
|
721
916
|
} else {
|
|
@@ -729,6 +924,19 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
729
924
|
} else {
|
|
730
925
|
setCanEditorBeInEditMode(true);
|
|
731
926
|
}
|
|
927
|
+
|
|
928
|
+
if (isEditorDisabledByParent || inheritedEditorMode === EDITOR_MODE__VIEW) {
|
|
929
|
+
setIsEditorViewOnly(true);
|
|
930
|
+
} else if (inheritedEditorMode === EDITOR_MODE__EDIT) {
|
|
931
|
+
const canEditInInheritedMode =
|
|
932
|
+
!canEditorViewOnly &&
|
|
933
|
+
userCanEdit &&
|
|
934
|
+
!disableEdit &&
|
|
935
|
+
(!canUser || canUser(EDIT)) &&
|
|
936
|
+
(!canRecordBeEdited || canRecordBeEdited(selection));
|
|
937
|
+
setIsEditorViewOnly(!canEditInInheritedMode);
|
|
938
|
+
}
|
|
939
|
+
|
|
732
940
|
setEditorMode(calculateEditorMode());
|
|
733
941
|
setLastSelection(selection);
|
|
734
942
|
|
|
@@ -738,7 +946,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
738
946
|
Promise.resolve().then(() => {
|
|
739
947
|
setIsIgnoreNextSelectionChange(false);
|
|
740
948
|
});
|
|
741
|
-
}, [selection]);
|
|
949
|
+
}, [selection, inheritedEditorMode, isEditorDisabledByParent]);
|
|
742
950
|
|
|
743
951
|
if (self) {
|
|
744
952
|
self.add = doAdd;
|
|
@@ -759,7 +967,13 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
759
967
|
setEditorMode(calculateEditorMode());
|
|
760
968
|
}
|
|
761
969
|
|
|
762
|
-
|
|
970
|
+
const editorModeContextValue = {
|
|
971
|
+
effectiveEditorMode: getEditorMode(),
|
|
972
|
+
isAnyAncestorUnsaved,
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
return <EditorModeContext.Provider value={editorModeContextValue}>
|
|
976
|
+
<WrappedComponent
|
|
763
977
|
{...props}
|
|
764
978
|
ref={ref}
|
|
765
979
|
disableWithEditor={false}
|
|
@@ -769,35 +983,38 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
769
983
|
isEditorShown={getIsEditorShown()}
|
|
770
984
|
getIsEditorShown={getIsEditorShown}
|
|
771
985
|
isEditorViewOnly={isEditorViewOnly}
|
|
986
|
+
isEditorModeControlledByParent={isEditorModeControlledByParent}
|
|
987
|
+
isEditorDisabledByParent={isEditorDisabledByParent}
|
|
772
988
|
isAdding={isAdding}
|
|
773
989
|
isSaving={isSaving}
|
|
774
990
|
editorMode={getEditorMode()}
|
|
775
991
|
getEditorMode={getEditorMode}
|
|
776
|
-
onEditMode={setEditMode}
|
|
777
|
-
onViewMode={setViewMode}
|
|
992
|
+
onEditMode={(isEditorModeControlledByParent || isEditorDisabledByParent) ? null : setEditMode}
|
|
993
|
+
onViewMode={(isEditorModeControlledByParent || isEditorDisabledByParent) ? null : setViewMode}
|
|
778
994
|
editorStateRef={editorStateRef}
|
|
779
995
|
setIsEditorShown={setIsEditorShown}
|
|
780
996
|
setIsIgnoreNextSelectionChange={setIsIgnoreNextSelectionChange}
|
|
781
|
-
onAdd={(!userCanEdit || disableAdd) ? null : doAdd}
|
|
782
|
-
onEdit={(!userCanEdit || disableEdit || (canRecordBeEdited && !canRecordBeEdited(selection))) ? null : doEdit}
|
|
783
|
-
onDelete={(!userCanEdit || disableDelete || (canRecordBeDeleted && !canRecordBeDeleted(selection))) ? null : doDelete}
|
|
784
|
-
onView={doView}
|
|
785
|
-
onDuplicate={doDuplicate}
|
|
997
|
+
onAdd={(isEditorDisabledByParent || isCrudBlockedByInheritedView || !userCanEdit || disableAdd) ? null : doAdd}
|
|
998
|
+
onEdit={(isEditorDisabledByParent || isCrudBlockedByInheritedView || !userCanEdit || disableEdit || (canRecordBeEdited && !canRecordBeEdited(selection))) ? null : doEdit}
|
|
999
|
+
onDelete={(isEditorDisabledByParent || isCrudBlockedByInheritedView || !userCanEdit || disableDelete || (canRecordBeDeleted && !canRecordBeDeleted(selection))) ? null : doDelete}
|
|
1000
|
+
onView={isEditorDisabledByParent ? null : doView}
|
|
1001
|
+
onDuplicate={(isEditorDisabledByParent || isCrudBlockedByInheritedView) ? null : doDuplicate}
|
|
786
1002
|
onEditorSave={doEditorSave}
|
|
787
1003
|
onEditorCancel={doEditorCancel}
|
|
788
|
-
onEditorDelete={(!userCanEdit || disableDelete) ? null : doEditorDelete}
|
|
1004
|
+
onEditorDelete={(isEditorDisabledByParent || isCrudBlockedByInheritedView || !userCanEdit || disableDelete) ? null : doEditorDelete}
|
|
789
1005
|
onEditorClose={doEditorClose}
|
|
790
1006
|
setWithEditListeners={setListeners}
|
|
791
1007
|
isEditor={true}
|
|
792
1008
|
userCanEdit={userCanEdit}
|
|
793
1009
|
userCanView={userCanView}
|
|
794
|
-
disableAdd={disableAdd}
|
|
795
|
-
disableEdit={disableEdit}
|
|
796
|
-
disableDelete={disableDelete}
|
|
797
|
-
disableDuplicate={disableDuplicate}
|
|
798
|
-
disableView ={disableView}
|
|
1010
|
+
disableAdd={disableAdd || isEditorDisabledByParent || isCrudBlockedByInheritedView}
|
|
1011
|
+
disableEdit={disableEdit || isEditorDisabledByParent || isCrudBlockedByInheritedView}
|
|
1012
|
+
disableDelete={disableDelete || isEditorDisabledByParent || isCrudBlockedByInheritedView}
|
|
1013
|
+
disableDuplicate={disableDuplicate || isEditorDisabledByParent || isCrudBlockedByInheritedView}
|
|
1014
|
+
disableView ={disableView || isEditorDisabledByParent}
|
|
799
1015
|
setSelection={setSelectionDecorated}
|
|
800
1016
|
isTree={isTree}
|
|
801
|
-
|
|
1017
|
+
/>
|
|
1018
|
+
</EditorModeContext.Provider>;
|
|
802
1019
|
});
|
|
803
1020
|
}
|
|
@@ -418,6 +418,9 @@ export default function withPdfButtons(WrappedComponent) {
|
|
|
418
418
|
},
|
|
419
419
|
];
|
|
420
420
|
_.each(buttons, (button) => {
|
|
421
|
+
if (!button) {
|
|
422
|
+
return; // guard against null/undefined
|
|
423
|
+
}
|
|
421
424
|
if (!_.find(additionalEditButtons, btn => button.key === btn.key)) {
|
|
422
425
|
additionalEditButtons.push(button);
|
|
423
426
|
}
|
|
@@ -68,6 +68,7 @@ export default function withPresetButtons(WrappedComponent) {
|
|
|
68
68
|
canDeleteRootNode = false,
|
|
69
69
|
isSideEditor = false,
|
|
70
70
|
canEditorViewOnly = false,
|
|
71
|
+
canRecordBeAdded, // fn(selection) returns bool on if the current record(s) can be added
|
|
71
72
|
canRecordBeEdited, // fn(selection) returns bool on if the current record(s) can be edited
|
|
72
73
|
canRecordBeDeleted, // fn(selection) returns bool on if the current record(s) can be deleted
|
|
73
74
|
canRecordBeDuplicated, // fn(selection) returns bool on if the current record(s) can be duplicated
|
|
@@ -239,6 +240,7 @@ export default function withPresetButtons(WrappedComponent) {
|
|
|
239
240
|
};
|
|
240
241
|
icon = Plus;
|
|
241
242
|
if (isNoSelectorSelected() ||
|
|
243
|
+
(canRecordBeAdded && !canRecordBeAdded(selection)) ||
|
|
242
244
|
(isTree && isEmptySelection())
|
|
243
245
|
) {
|
|
244
246
|
isDisabled = true;
|
|
@@ -50,6 +50,7 @@ function Panel(props) {
|
|
|
50
50
|
disableTitleChange = false,
|
|
51
51
|
|
|
52
52
|
// Content
|
|
53
|
+
renderWhileCollapsed = false,
|
|
53
54
|
topToolbar = null,
|
|
54
55
|
children = null,
|
|
55
56
|
bottomToolbar = null,
|
|
@@ -139,14 +140,7 @@ function Panel(props) {
|
|
|
139
140
|
className += ' ' + filteredClassName;
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
|
|
143
|
-
{...testProps(self?.reference)}
|
|
144
|
-
className={className}
|
|
145
|
-
style={style}
|
|
146
|
-
>
|
|
147
|
-
{isDisabled && <Mask />}
|
|
148
|
-
{headerComponent}
|
|
149
|
-
{!isCollapsed && <>
|
|
143
|
+
let content = <>
|
|
150
144
|
{topToolbar}
|
|
151
145
|
<VStack
|
|
152
146
|
className={clsx(
|
|
@@ -169,7 +163,41 @@ function Panel(props) {
|
|
|
169
163
|
</VStack>
|
|
170
164
|
{bottomToolbar}
|
|
171
165
|
{footer}
|
|
172
|
-
|
|
166
|
+
</>;
|
|
167
|
+
if (renderWhileCollapsed) {
|
|
168
|
+
content = <Box
|
|
169
|
+
className={clsx(
|
|
170
|
+
'Panel-Box',
|
|
171
|
+
...(isCollapsed ?
|
|
172
|
+
[
|
|
173
|
+
'w-[0px]',
|
|
174
|
+
'h-[0px]',
|
|
175
|
+
'overflow-hidden',
|
|
176
|
+
] :
|
|
177
|
+
[
|
|
178
|
+
'w-full',
|
|
179
|
+
'h-full',
|
|
180
|
+
])
|
|
181
|
+
)}
|
|
182
|
+
>
|
|
183
|
+
{content}
|
|
184
|
+
</Box>;
|
|
185
|
+
} else {
|
|
186
|
+
if (isCollapsed) {
|
|
187
|
+
// hide the content when collapsed
|
|
188
|
+
content = null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
return <VStackNative
|
|
194
|
+
{...testProps(self?.reference)}
|
|
195
|
+
className={className}
|
|
196
|
+
style={style}
|
|
197
|
+
>
|
|
198
|
+
{isDisabled && <Mask />}
|
|
199
|
+
{headerComponent}
|
|
200
|
+
{content}
|
|
173
201
|
</VStackNative>;
|
|
174
202
|
|
|
175
203
|
}
|
|
@@ -92,18 +92,18 @@ export default function PmEventsEditor(props) {
|
|
|
92
92
|
case PM_EVENT_TYPES__INITIAL:
|
|
93
93
|
case PM_EVENT_TYPES__WORK_ORDER:
|
|
94
94
|
case PM_EVENT_TYPES__ALERT:
|
|
95
|
+
case PM_EVENT_TYPES__COMPLETE:
|
|
95
96
|
setIsIntervalHidden(true);
|
|
96
97
|
setIsDateHidden(true);
|
|
97
98
|
setIsMeterReadingHidden(false);
|
|
98
99
|
setIsDetailsHidden(false);
|
|
99
100
|
setIsPmTechnicianHidden(true);
|
|
100
101
|
break;
|
|
101
|
-
case PM_EVENT_TYPES__COMPLETE:
|
|
102
102
|
case PM_EVENT_TYPES__RESET:
|
|
103
103
|
setIsIntervalHidden(true);
|
|
104
104
|
setIsDateHidden(true);
|
|
105
|
-
setIsMeterReadingHidden(
|
|
106
|
-
setIsDetailsHidden(
|
|
105
|
+
setIsMeterReadingHidden(true);
|
|
106
|
+
setIsDetailsHidden(true);
|
|
107
107
|
setIsPmTechnicianHidden(true);
|
|
108
108
|
break;
|
|
109
109
|
case PM_EVENT_TYPES__DELAY_BY_DAYS:
|
|
@@ -17,7 +17,7 @@ import ClockRegular from '../../Icons/ClockRegular.js';
|
|
|
17
17
|
import OilCan from '../../Icons/OilCan.js';
|
|
18
18
|
import TabBar from '../../Tab/TabBar.js';
|
|
19
19
|
import TreeSpecific from '../Layout/TreeSpecific/TreeSpecific.js';
|
|
20
|
-
import UpcomingPmsGrid from '
|
|
20
|
+
import UpcomingPmsGrid from '@src/Components/Grid/UpcomingPmsGrid.js';
|
|
21
21
|
import PmEventsFilteredGridEditor from '@src/Components/Grid/PmEventsFilteredGridEditor.js';
|
|
22
22
|
import PmEventsFilteredSideGridEditor from '@src/Components/Grid/PmEventsFilteredSideGridEditor.js';
|
|
23
23
|
import _ from 'lodash';
|
|
@@ -31,6 +31,7 @@ function ManagerScreen(props) {
|
|
|
31
31
|
[isModeSet, setIsModeSet] = useState(false),
|
|
32
32
|
[allowSideBySide, setAllowSideBySide] = useState(false),
|
|
33
33
|
[mode, setModeRaw] = useState(SCREEN_MODES__SIDE),
|
|
34
|
+
isDisabled = propsToPass._panel?.isDisabled ?? false, // this is kind of a hack, since there's no Panel, but it works
|
|
34
35
|
actualMode = (!allowSideBySide || mode === SCREEN_MODES__FULL) ? SCREEN_MODES__FULL : SCREEN_MODES__SIDE,
|
|
35
36
|
setMode = (newMode) => {
|
|
36
37
|
if (!allowSideBySide && newMode === SCREEN_MODES__SIDE) {
|
|
@@ -97,7 +98,7 @@ function ManagerScreen(props) {
|
|
|
97
98
|
onFullWidth={() => setMode(SCREEN_MODES__FULL)}
|
|
98
99
|
onSideBySide={() => setMode(SCREEN_MODES__SIDE)}
|
|
99
100
|
/>
|
|
100
|
-
{isRendered && isModeSet && whichComponent}
|
|
101
|
+
{isRendered && isModeSet && !isDisabled && whichComponent}
|
|
101
102
|
</VStackNative>;
|
|
102
103
|
}
|
|
103
104
|
|
|
@@ -11,15 +11,17 @@ const PaginationToolbar = forwardRef((props, ref) => {
|
|
|
11
11
|
const {
|
|
12
12
|
toolbarItems = [],
|
|
13
13
|
disablePageSize = false,
|
|
14
|
+
disableMinimizing = false,
|
|
14
15
|
minimize,
|
|
15
16
|
} = props,
|
|
16
17
|
[minimizeLocal, setMinimizeLocal] = useState(minimize),
|
|
17
18
|
propsToPass = _.omit(props, 'toolbarItems'),
|
|
18
19
|
showPagination = true,//props.Repository?.totalPages > 1,
|
|
19
20
|
onLayout = (e) => {
|
|
20
|
-
if (minimize) {
|
|
21
|
-
return;
|
|
21
|
+
if (minimize || disableMinimizing) {
|
|
22
|
+
return;
|
|
22
23
|
}
|
|
24
|
+
|
|
23
25
|
// Note to future self: this is using hard-coded values.
|
|
24
26
|
// Eventually might want to make it responsive to actual sizes
|
|
25
27
|
|
|
@@ -32,7 +34,7 @@ const PaginationToolbar = forwardRef((props, ref) => {
|
|
|
32
34
|
threshold = pagingToolbarMinwidth + toolbarItemsMinwidth,
|
|
33
35
|
shouldMinimize = width < threshold;
|
|
34
36
|
|
|
35
|
-
if (shouldMinimize !==
|
|
37
|
+
if (shouldMinimize !== minimizeLocal) {
|
|
36
38
|
setMinimizeLocal(shouldMinimize);
|
|
37
39
|
}
|
|
38
40
|
};
|
|
@@ -72,6 +72,7 @@ function Viewer(props) {
|
|
|
72
72
|
onEditMode,
|
|
73
73
|
onClose,
|
|
74
74
|
onDelete,
|
|
75
|
+
isEditorModeControlledByParent = false,
|
|
75
76
|
|
|
76
77
|
// parent container
|
|
77
78
|
selectorId,
|
|
@@ -350,9 +351,11 @@ function Viewer(props) {
|
|
|
350
351
|
return <HStack key={ix} className="Viewer-HStack4 px-2 pb-1">{element}</HStack>;
|
|
351
352
|
},
|
|
352
353
|
buildAncillary = () => {
|
|
353
|
-
const
|
|
354
|
+
const
|
|
355
|
+
validAncillaryItems = _.filter(ancillaryItems, (item) => !!item), // filter out any null/undefined items
|
|
356
|
+
components = [];
|
|
354
357
|
setAncillaryButtons([]);
|
|
355
|
-
if (
|
|
358
|
+
if (validAncillaryItems.length) {
|
|
356
359
|
|
|
357
360
|
// add the "scroll to top" button
|
|
358
361
|
getAncillaryButtons().push({
|
|
@@ -363,7 +366,7 @@ function Viewer(props) {
|
|
|
363
366
|
tooltip: 'Scroll to top',
|
|
364
367
|
});
|
|
365
368
|
|
|
366
|
-
_.each(
|
|
369
|
+
_.each(validAncillaryItems, (item, ix) => {
|
|
367
370
|
let {
|
|
368
371
|
type,
|
|
369
372
|
title = null,
|
|
@@ -561,7 +564,7 @@ function Viewer(props) {
|
|
|
561
564
|
|
|
562
565
|
<Toolbar className="justify-end">
|
|
563
566
|
<HStack className="flex-1 items-center">
|
|
564
|
-
|
|
567
|
+
<Text className="text-[20px] ml-1 text-grey-500">{isEditorModeControlledByParent ? 'View Mode (Inherited)' : 'View Mode'}</Text>
|
|
565
568
|
</HStack>
|
|
566
569
|
{onEditMode && (!canUser || canUser(EDIT)) &&
|
|
567
570
|
<Button
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PM_SCHEDULES__ONE_OFF = 200;
|
|
@@ -388,6 +388,19 @@ function AttachmentsElement(props) {
|
|
|
388
388
|
downloadInBackground(url, {}, Attachments.headers);
|
|
389
389
|
}
|
|
390
390
|
},
|
|
391
|
+
onDownloadAll = () => {
|
|
392
|
+
if (!model || _.isNil(modelid.current) || _.isArray(modelid.current)) {
|
|
393
|
+
alert('Cannot download all attachments without a single selected model and model id.');
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const url = Attachments.api.baseURL + 'Attachments/downloadAll/' + encodeURIComponent(model) + '/' + encodeURIComponent(modelid.current);
|
|
398
|
+
if (isPwa) {
|
|
399
|
+
alert('Files cannot be downloaded and viewed within an iOS PWA. Please use the Safari browser instead.');
|
|
400
|
+
} else {
|
|
401
|
+
downloadInBackground(url, {}, Attachments.headers);
|
|
402
|
+
}
|
|
403
|
+
},
|
|
391
404
|
|
|
392
405
|
// dropzone
|
|
393
406
|
onDropzoneChange = async (files) => {
|
|
@@ -1087,6 +1100,19 @@ function AttachmentsElement(props) {
|
|
|
1087
1100
|
)}
|
|
1088
1101
|
tooltip="List View"
|
|
1089
1102
|
/>
|
|
1103
|
+
<Box className="spacer flex-1" />
|
|
1104
|
+
<IconButton
|
|
1105
|
+
onPress={onDownloadAll}
|
|
1106
|
+
icon={Download}
|
|
1107
|
+
className={clsx(
|
|
1108
|
+
'w-[25px]',
|
|
1109
|
+
'h-[25px]',
|
|
1110
|
+
'px-[2px]',
|
|
1111
|
+
'py-[2px]',
|
|
1112
|
+
)}
|
|
1113
|
+
isDisabled={!model || _.isNil(modelid.current) || _.isArray(modelid.current) || files.length === 0}
|
|
1114
|
+
tooltip="Download All"
|
|
1115
|
+
/>
|
|
1090
1116
|
</HStack>
|
|
1091
1117
|
|
|
1092
1118
|
{content}
|