@onehat/ui 0.4.103 → 0.4.104
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 +9 -6
- 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 +18 -1
- 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 +5 -3
- package/src/Constants/PmSchedules.js +1 -0
- package/src/Functions/buildAdditionalButtons.js +5 -0
|
@@ -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 () => {
|
|
@@ -253,8 +253,9 @@ function Form(props) {
|
|
|
253
253
|
'border-r-grey-200',
|
|
254
254
|
'px-1',
|
|
255
255
|
styles.INLINE_EDITOR_MIN_WIDTH,
|
|
256
|
-
)
|
|
257
|
-
|
|
256
|
+
),
|
|
257
|
+
validColumnsConfig = _.filter(columnsConfig, (config) => !!config); // filter out any null/undefined configs
|
|
258
|
+
_.each(validColumnsConfig, (config, ix) => {
|
|
258
259
|
let {
|
|
259
260
|
fieldName,
|
|
260
261
|
isEditable = false,
|
|
@@ -273,7 +274,7 @@ function Form(props) {
|
|
|
273
274
|
type,
|
|
274
275
|
editorTypeProps = {},
|
|
275
276
|
viewerTypeProps = {};
|
|
276
|
-
|
|
277
|
+
|
|
277
278
|
if (isHidden) {
|
|
278
279
|
return;
|
|
279
280
|
}
|
|
@@ -965,9 +966,11 @@ function Form(props) {
|
|
|
965
966
|
/>;
|
|
966
967
|
},
|
|
967
968
|
buildAncillary = () => {
|
|
968
|
-
const
|
|
969
|
+
const
|
|
970
|
+
validAncillaryItems = _.filter(ancillaryItems, (item) => !!item), // filter out any null/undefined items
|
|
971
|
+
components = [];
|
|
969
972
|
setAncillaryButtons([]);
|
|
970
|
-
if (
|
|
973
|
+
if (validAncillaryItems.length) {
|
|
971
974
|
|
|
972
975
|
// add the "scroll to top" button
|
|
973
976
|
getAncillaryButtons().push({
|
|
@@ -978,7 +981,7 @@ function Form(props) {
|
|
|
978
981
|
tooltip: 'Scroll to top',
|
|
979
982
|
});
|
|
980
983
|
|
|
981
|
-
_.each(
|
|
984
|
+
_.each(validAncillaryItems, (item, ix) => {
|
|
982
985
|
let {
|
|
983
986
|
type,
|
|
984
987
|
title = null,
|
|
@@ -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) {
|
|
@@ -47,6 +47,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
47
47
|
},
|
|
48
48
|
editorType,
|
|
49
49
|
onAdd,
|
|
50
|
+
onBeforeAdd,
|
|
50
51
|
onChange, // any kind of crud change
|
|
51
52
|
onBeforeDelete,
|
|
52
53
|
onDelete,
|
|
@@ -218,10 +219,26 @@ export default function withEditor(WrappedComponent, isTree = false) {
|
|
|
218
219
|
}
|
|
219
220
|
|
|
220
221
|
if (getListeners().onBeforeAdd) {
|
|
221
|
-
|
|
222
|
+
// This listener is set by child components using setWithEditListeners()
|
|
223
|
+
const listenerResult = await getListeners().onBeforeAdd(addValues);
|
|
224
|
+
if (listenerResult === false) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (listenerResult) {
|
|
228
|
+
// allow the listener to override the addValues by returning an object
|
|
229
|
+
addValues = listenerResult;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (onBeforeAdd) {
|
|
233
|
+
// This listener is set by parent components using a prop
|
|
234
|
+
const listenerResult = await onBeforeAdd(addValues);
|
|
222
235
|
if (listenerResult === false) {
|
|
223
236
|
return;
|
|
224
237
|
}
|
|
238
|
+
if (listenerResult) {
|
|
239
|
+
// allow the listener to override the addValues by returning an object
|
|
240
|
+
addValues = listenerResult;
|
|
241
|
+
}
|
|
225
242
|
}
|
|
226
243
|
|
|
227
244
|
if (isTree) {
|
|
@@ -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
|
};
|
|
@@ -350,9 +350,11 @@ function Viewer(props) {
|
|
|
350
350
|
return <HStack key={ix} className="Viewer-HStack4 px-2 pb-1">{element}</HStack>;
|
|
351
351
|
},
|
|
352
352
|
buildAncillary = () => {
|
|
353
|
-
const
|
|
353
|
+
const
|
|
354
|
+
validAncillaryItems = _.filter(ancillaryItems, (item) => !!item), // filter out any null/undefined items
|
|
355
|
+
components = [];
|
|
354
356
|
setAncillaryButtons([]);
|
|
355
|
-
if (
|
|
357
|
+
if (validAncillaryItems.length) {
|
|
356
358
|
|
|
357
359
|
// add the "scroll to top" button
|
|
358
360
|
getAncillaryButtons().push({
|
|
@@ -363,7 +365,7 @@ function Viewer(props) {
|
|
|
363
365
|
tooltip: 'Scroll to top',
|
|
364
366
|
});
|
|
365
367
|
|
|
366
|
-
_.each(
|
|
368
|
+
_.each(validAncillaryItems, (item, ix) => {
|
|
367
369
|
let {
|
|
368
370
|
type,
|
|
369
371
|
title = null,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PM_SCHEDULES__ONE_OFF = 200;
|