@onehat/ui 0.4.102 → 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/Accordion/Accordion.js +65 -6
- package/src/Components/Container/Container.js +10 -4
- package/src/Components/Form/Field/Combo/Combo.js +8 -2
- package/src/Components/Form/Form.js +17 -9
- package/src/Components/Grid/Grid.js +234 -154
- 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 +20 -6
- package/src/Components/Icons/ArrowsLeftRight.js +10 -0
- package/src/Components/Icons/Bar.js +10 -0
- package/src/Components/Icons/Box.js +11 -0
- package/src/Components/Icons/BoxOpen.js +11 -0
- package/src/Components/Icons/Bucket.js +10 -0
- package/src/Components/Icons/Bump.js +21 -0
- package/src/Components/Icons/Calculator.js +12 -0
- package/src/Components/Icons/Dots.js +20 -0
- package/src/Components/Icons/Fleets.js +26 -0
- package/src/Components/Icons/Microchip.js +12 -0
- package/src/Components/Icons/Num1.js +10 -0
- package/src/Components/Icons/Num2.js +10 -0
- package/src/Components/Icons/Num3.js +10 -0
- package/src/Components/Icons/Num4.js +10 -0
- package/src/Components/Icons/OilCan.js +11 -0
- package/src/Components/Icons/Operations.js +10 -0
- package/src/Components/Icons/OverduePms.js +10 -0
- package/src/Components/Icons/SackDollar.js +11 -0
- package/src/Components/Icons/ShortBar.js +15 -0
- package/src/Components/Icons/Tower.js +10 -0
- package/src/Components/Icons/UpcomingPms.js +10 -0
- package/src/Components/Layout/ScreenHeader.js +35 -3
- package/src/Components/Layout/SetupButton.js +31 -0
- package/src/Components/Layout/UserIndicator.js +35 -0
- package/src/Components/Panel/Panel.js +37 -9
- package/src/Components/Pms/Editor/BumpPmsEditor.js +9 -0
- package/src/Components/Pms/Editor/MetersEditor.js +173 -0
- package/src/Components/Pms/Editor/PmEventsEditor.js +291 -0
- package/src/Components/Pms/Grid/UpcomingPmsGrid.js +569 -0
- package/src/Components/Pms/Layout/TreeSpecific/MakeTreeSelection.js +11 -0
- package/src/Components/Pms/Layout/TreeSpecific/TreeSpecific.js +30 -0
- package/src/Components/Pms/Modals/BulkAssignTechnician.js +104 -0
- package/src/Components/Pms/Screens/PmsManager.js +136 -0
- package/src/Components/Pms/Window/BumpPmsEditorWindow.js +25 -0
- package/src/Components/Screens/Manager.js +5 -1
- package/src/Components/Toolbar/PaginationToolbar.js +5 -3
- package/src/Components/Tree/Tree.js +15 -6
- package/src/Components/Viewer/PmCalcDebugViewer.js +164 -146
- package/src/Components/Viewer/TextWithLinks.js +9 -1
- package/src/Components/Viewer/Viewer.js +43 -33
- package/src/Constants/PmSchedules.js +1 -0
- package/src/Functions/buildAdditionalButtons.js +5 -0
- package/src/Functions/flatten.js +39 -0
- package/src/Functions/verifyCanCrudPmEvents.js +33 -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
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { useState, useEffect, useRef, } from 'react';
|
|
2
2
|
import {
|
|
3
|
+
Box,
|
|
3
4
|
HStack,
|
|
4
|
-
|
|
5
|
+
Icon,
|
|
5
6
|
Pressable,
|
|
7
|
+
ScrollView,
|
|
8
|
+
Text,
|
|
9
|
+
TextNative,
|
|
6
10
|
VStack,
|
|
7
11
|
VStackNative,
|
|
8
12
|
} from '@project-components/Gluestack';
|
|
9
13
|
import clsx from 'clsx';
|
|
14
|
+
import Plus from '../Icons/Plus.js';
|
|
15
|
+
import Minus from '../Icons/Minus.js';
|
|
10
16
|
import inArray from '../../Functions/inArray.js';
|
|
11
17
|
import emptyFn from '../../Functions/emptyFn.js';
|
|
18
|
+
import UiGlobals from '../../UiGlobals.js';
|
|
12
19
|
import _ from 'lodash';
|
|
13
20
|
|
|
14
21
|
// The Accordion has two modes.
|
|
@@ -25,11 +32,61 @@ import _ from 'lodash';
|
|
|
25
32
|
|
|
26
33
|
export default function Accordion(props) {
|
|
27
34
|
const {
|
|
35
|
+
styles = UiGlobals.styles,
|
|
28
36
|
sections = [],
|
|
29
37
|
activeSections = [],
|
|
30
38
|
setActiveSections = emptyFn,
|
|
31
|
-
renderHeader =
|
|
32
|
-
|
|
39
|
+
renderHeader = (section, ix, isActive) => {
|
|
40
|
+
return <HStack
|
|
41
|
+
className={clsx(
|
|
42
|
+
'Header',
|
|
43
|
+
'bg-grey-300',
|
|
44
|
+
'items-center',
|
|
45
|
+
'justify-start',
|
|
46
|
+
'py-1',
|
|
47
|
+
'px-2',
|
|
48
|
+
'border-b-grey-400',
|
|
49
|
+
'border-b-1',
|
|
50
|
+
styles.PANEL_HEADER_BG,
|
|
51
|
+
)}
|
|
52
|
+
>
|
|
53
|
+
{/* <Text className="text-white flex-1">{section.header}</Text> */}
|
|
54
|
+
|
|
55
|
+
<TextNative
|
|
56
|
+
numberOfLines={1}
|
|
57
|
+
ellipsizeMode="head"
|
|
58
|
+
className={clsx(
|
|
59
|
+
'Header-TextNative1',
|
|
60
|
+
'flex-1',
|
|
61
|
+
'font-bold',
|
|
62
|
+
styles.PANEL_HEADER_TEXT_CLASSNAME,
|
|
63
|
+
)}>{section.header}</TextNative>
|
|
64
|
+
<Icon
|
|
65
|
+
as={isActive ? Minus : Plus}
|
|
66
|
+
className={clsx(
|
|
67
|
+
'text-black',
|
|
68
|
+
)}
|
|
69
|
+
/>
|
|
70
|
+
</HStack>;
|
|
71
|
+
},
|
|
72
|
+
unmountInactiveContent = true,
|
|
73
|
+
renderContent = (section, ix, isActive, ref) => {
|
|
74
|
+
if (unmountInactiveContent) {
|
|
75
|
+
if (!isActive) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return section.content;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// This keeps all content rendered, just hidden (zero height) if it's inActive
|
|
82
|
+
let className = 'w-full overflow-hidden';
|
|
83
|
+
if (!isActive) {
|
|
84
|
+
className += ' h-[0px]';
|
|
85
|
+
}
|
|
86
|
+
return <Box className={className}>
|
|
87
|
+
{section.content}
|
|
88
|
+
</Box>;
|
|
89
|
+
},
|
|
33
90
|
onAnimationEnd = emptyFn,
|
|
34
91
|
onLayout,
|
|
35
92
|
onlyOne = true,
|
|
@@ -67,7 +124,9 @@ export default function Accordion(props) {
|
|
|
67
124
|
|
|
68
125
|
// TODO: Animate height. Possible help here: https://stackoverflow.com/a/57333550 and https://stackoverflow.com/a/64797961
|
|
69
126
|
if (isActive) {
|
|
70
|
-
|
|
127
|
+
if (onlyOne) {
|
|
128
|
+
rowProps.flex = 1;
|
|
129
|
+
}
|
|
71
130
|
} else {
|
|
72
131
|
rowProps.h = 0;
|
|
73
132
|
rowProps.overflow = 'hidden'; // otherwise some elements mistakenly show
|
|
@@ -94,7 +153,7 @@ export default function Accordion(props) {
|
|
|
94
153
|
>
|
|
95
154
|
{header}
|
|
96
155
|
</Pressable>
|
|
97
|
-
<HStack {...rowProps}
|
|
156
|
+
<HStack {...rowProps}>
|
|
98
157
|
{content}
|
|
99
158
|
</HStack>
|
|
100
159
|
</VStack>;
|
|
@@ -129,7 +188,7 @@ export default function Accordion(props) {
|
|
|
129
188
|
keyboardShouldPersistTaps="always"
|
|
130
189
|
className="Accordion-ScrollView flex-1 w-full"
|
|
131
190
|
contentContainerStyle={{
|
|
132
|
-
height: '100%',
|
|
191
|
+
height: onlyOne ? '100%' : undefined,
|
|
133
192
|
}}
|
|
134
193
|
>
|
|
135
194
|
<VStackNative
|
|
@@ -125,16 +125,20 @@ function Container(props) {
|
|
|
125
125
|
localSouthIsCollapsedRef = useRef(southInitialIsCollapsed),
|
|
126
126
|
localEastIsCollapsedRef = useRef(eastInitialIsCollapsed),
|
|
127
127
|
localWestIsCollapsedRef = useRef(westInitialIsCollapsed),
|
|
128
|
+
isSplitterDraggingRef = useRef(false),
|
|
128
129
|
onLayout = async (e) => {
|
|
129
|
-
|
|
130
|
+
if (isSplitterDraggingRef.current) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
130
133
|
if (id) {
|
|
131
134
|
// save prevScreenSize if changed
|
|
132
135
|
const
|
|
133
|
-
height = parseFloat(e.nativeEvent.layout.height),
|
|
134
|
-
width = parseFloat(e.nativeEvent.layout.width),
|
|
136
|
+
height = windowSize?.height ?? parseFloat(e.nativeEvent.layout.height),
|
|
137
|
+
width = windowSize?.width ?? parseFloat(e.nativeEvent.layout.width),
|
|
135
138
|
key = id + '-prevScreenSize',
|
|
136
139
|
prevScreenSize = await getSaved(key);
|
|
137
|
-
|
|
140
|
+
const hasChanged = !prevScreenSize || Math.abs((prevScreenSize.width ?? 0) - width) > 1 || Math.abs((prevScreenSize.height ?? 0) - height) > 1;
|
|
141
|
+
if (hasChanged) {
|
|
138
142
|
await setSaved(key, {
|
|
139
143
|
height,
|
|
140
144
|
width,
|
|
@@ -300,10 +304,12 @@ function Container(props) {
|
|
|
300
304
|
}
|
|
301
305
|
},
|
|
302
306
|
onSplitterDragStart = () => {
|
|
307
|
+
isSplitterDraggingRef.current = true;
|
|
303
308
|
setIsComponentsDisabled(true);
|
|
304
309
|
},
|
|
305
310
|
onSplitterDragStop = (delta, which) => {
|
|
306
311
|
setIsComponentsDisabled(false);
|
|
312
|
+
isSplitterDraggingRef.current = false;
|
|
307
313
|
switch(which) {
|
|
308
314
|
case 'north':
|
|
309
315
|
onNorthResize(delta);
|
|
@@ -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,
|
|
@@ -1213,6 +1216,10 @@ function Form(props) {
|
|
|
1213
1216
|
style.maxHeight = maxHeight;
|
|
1214
1217
|
}
|
|
1215
1218
|
|
|
1219
|
+
const
|
|
1220
|
+
// allow horizontal layouts only if there are top-level columns, and screen is wide enough
|
|
1221
|
+
hasTopLevelColumns = _.some(items, (item) => item?.type === 'Column'),
|
|
1222
|
+
shouldUseHorizontalFormLayout = !isItemsCustomLayout && hasTopLevelColumns && containerWidth >= styles.FORM_ONE_COLUMN_THRESHOLD;
|
|
1216
1223
|
let modeHeader = null,
|
|
1217
1224
|
formButtons = null,
|
|
1218
1225
|
scrollButtons = null,
|
|
@@ -1246,8 +1253,8 @@ function Form(props) {
|
|
|
1246
1253
|
formComponents = buildFromItems();
|
|
1247
1254
|
const formAncillaryComponents = buildAncillary();
|
|
1248
1255
|
editor = <>
|
|
1249
|
-
{
|
|
1250
|
-
{
|
|
1256
|
+
{shouldUseHorizontalFormLayout ? <HStack className="Form-formComponents-HStack w-full min-w-0 p-4 gap-4 justify-start items-stretch">{formComponents}</HStack> : null}
|
|
1257
|
+
{!shouldUseHorizontalFormLayout ? <VStack className="Form-formComponents-VStack p-4">{formComponents}</VStack> : null}
|
|
1251
1258
|
{formAncillaryComponents.length ? <VStack className="Form-AncillaryComponents m-2 pt-4 px-2">{formAncillaryComponents}</VStack> : null}
|
|
1252
1259
|
</>;
|
|
1253
1260
|
|
|
@@ -1532,7 +1539,8 @@ function Form(props) {
|
|
|
1532
1539
|
onScroll={onScroll}
|
|
1533
1540
|
scrollEventThrottle={16 /* ms */}
|
|
1534
1541
|
contentContainerStyle={{
|
|
1535
|
-
|
|
1542
|
+
width: '100%',
|
|
1543
|
+
minWidth: '100%',
|
|
1536
1544
|
}}
|
|
1537
1545
|
>
|
|
1538
1546
|
{scrollToTopAnchor}
|