@eohjsc/react-native-smart-city 0.7.24 → 0.7.26
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/index.js +2 -0
- package/package.json +1 -1
- package/src/Images/Common/search-menu.svg +7 -0
- package/src/commons/ActionGroup/OnOffTemplate/OnOffButtonTemplateStyle.js +2 -1
- package/src/commons/ActionGroup/OneBigButtonTemplateStyle.js +1 -2
- package/src/commons/ActionGroup/SliderRangeTemplate.js +1 -3
- package/src/commons/ActionGroup/TerminalBoxTemplate.js +1 -4
- package/src/commons/ActionGroup/TextBoxTemplate.js +1 -5
- package/src/commons/ActionGroup/ThreeButtonTemplate/components/ThreeButtonDefaultStyles.js +1 -1
- package/src/commons/ActionGroup/__test__/index.test.js +51 -0
- package/src/commons/ActionGroup/index.js +4 -0
- package/src/commons/Dashboard/MyDashboardDevice/__test__/index.test.js +171 -0
- package/src/commons/Dashboard/MyDashboardDevice/index.js +218 -0
- package/src/commons/Dashboard/MyDashboardDevice/styles.js +60 -0
- package/src/commons/Dashboard/MyPinnedSharedUnit/index.js +0 -10
- package/src/commons/Dashboard/MyUnit/__test__/MyUnit.test.js +114 -48
- package/src/commons/Dashboard/MyUnit/index.js +74 -27
- package/src/commons/Dashboard/MyUnit/styles.js +16 -1
- package/src/commons/DateTimeRangeChange/index.js +1 -1
- package/src/commons/Device/ItemDevice.js +12 -3
- package/src/commons/Device/ItemDeviceWrapper.js +10 -2
- package/src/commons/SelectUnit/index.js +19 -5
- package/src/commons/SelectUnit/styles.js +0 -1
- package/src/commons/SubUnit/DeviceTemplate/DeviceTemplate.js +8 -3
- package/src/commons/Widgets/IFrame/IFrameStyles.js +1 -0
- package/src/commons/Widgets/IFrameWithConfig/IFrameWithConfigStyles.js +1 -0
- package/src/configs/API.js +11 -0
- package/src/configs/AccessibilityLabel.js +2 -0
- package/src/configs/Constants.js +3 -0
- package/src/context/actionType.ts +4 -0
- package/src/context/mockStore.ts +5 -0
- package/src/context/reducer.ts +30 -5
- package/src/iot/mqtt.js +163 -47
- package/src/navigations/UnitStack.js +22 -0
- package/src/screens/Device/__test__/detail.test.js +42 -1
- package/src/screens/Device/__test__/mqttDetail.test.js +411 -190
- package/src/screens/Device/__test__/sensorDisplayItem.test.js +27 -0
- package/src/screens/Device/components/DonutChart.js +5 -14
- package/src/screens/Device/components/SensorDisplayItem.js +92 -61
- package/src/screens/Device/components/VisualChart.js +0 -12
- package/src/screens/Device/detail.js +50 -14
- package/src/screens/Device/hooks/useDashboardDevice.js +34 -0
- package/src/screens/Device/styles.js +16 -0
- package/src/screens/SubUnit/AddSubUnit.js +18 -8
- package/src/screens/SubUnit/__test__/AddSubUnit.test.js +5 -3
- package/src/screens/Unit/GoToDetailUnit.js +30 -0
- package/src/screens/Unit/__test__/GoToDetailUnit.test.js +103 -0
- package/src/utils/FactoryGateway.js +105 -0
- package/src/utils/I18n/translations/en.js +4 -0
- package/src/utils/I18n/translations/vi.js +4 -0
- package/src/utils/Route/index.js +1 -0
- package/src/utils/Storage.js +18 -4
- package/src/utils/Utils.js +2 -1
- package/src/utils/Functions/preloadImages.js +0 -38
package/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { initSCConfig } from './src/configs';
|
|
|
14
14
|
import AutomateStack from './src/navigations/AutomateStack';
|
|
15
15
|
import NotificationStack from './src/navigations/NotificationStack';
|
|
16
16
|
import MyPinnedSharedUnit from './src/commons/Dashboard/MyPinnedSharedUnit';
|
|
17
|
+
import MyDashboardDevice from './src/commons/Dashboard/MyDashboardDevice';
|
|
17
18
|
import MyUnit from './src/commons/Dashboard/MyUnit';
|
|
18
19
|
import SharedUnit from './src/commons/Unit/SharedUnit';
|
|
19
20
|
import { Action } from './src/context/actionType';
|
|
@@ -41,6 +42,7 @@ export {
|
|
|
41
42
|
NotificationStack,
|
|
42
43
|
MyPinnedSharedUnit,
|
|
43
44
|
SharedUnit,
|
|
45
|
+
MyDashboardDevice,
|
|
44
46
|
MyUnit,
|
|
45
47
|
SCWrapper,
|
|
46
48
|
Action,
|
package/package.json
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->
|
|
3
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
|
4
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve">
|
|
5
|
+
<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
|
|
6
|
+
<g><g><path fill="#000000" d="M14.4,58.3L14.4,58.3h96.3h0.6v0c2.1,0.3,3.7,2.2,3.7,4.4c0,2.3-1.6,4.1-3.7,4.4v0H111l-0.4,0l-0.4,0H14.7l-0.4,0c-2.4,0-4.3-2-4.3-4.5C10,60.3,12,58.3,14.4,58.3z M14.4,106.9L14.4,106.9h96.3h0.6v0c2.1,0.3,3.7,2.2,3.7,4.4c0,2.3-1.6,4.1-3.7,4.4v0H111l-0.4,0l-0.4,0H14.7l-0.4,0c-2.4,0-4.3-2-4.3-4.5C10,108.9,12,106.9,14.4,106.9z M14.4,155.6L14.4,155.6l153.7,0h3.4h0.6v0c2.1,0.3,3.7,2.2,3.7,4.4c0,2.2-1.6,4.1-3.7,4.4v0h-0.3l-0.4,0l-0.4,0h-2.8l-153.6,0l-0.4,0c-2.4,0-4.3-2-4.3-4.5C10,157.6,12,155.6,14.4,155.6z M14.4,204.2L14.4,204.2l153.7,0h3.4h0.6v0c2.1,0.3,3.7,2.2,3.7,4.4s-1.6,4.1-3.7,4.4v0h-0.3l-0.4,0l-0.4,0h-2.8l-153.6,0l-0.4,0c-2.4,0-4.3-2-4.3-4.5C10,206.2,12,204.2,14.4,204.2z M176.2,42.9c14,0,26.8,5.7,35.9,14.9C221.3,67,227,79.7,227,93.7c0,14-5.7,26.8-14.9,35.9c-1.8,1.8-3.7,3.5-5.8,5l39,67.5c1.4,2.4,0.6,5.4-1.8,6.7c-2.4,1.4-5.4,0.6-6.7-1.8l-38.9-67.4c-6.6,3.1-13.9,4.8-21.7,4.8c-14,0-26.8-5.7-35.9-14.9c-9.2-9.2-14.9-21.9-14.9-35.9c0-14,5.7-26.8,14.9-35.9C149.4,48.6,162.1,42.9,176.2,42.9L176.2,42.9z M205.1,64.8c-7.4-7.4-17.7-12-28.9-12c-11.3,0-21.5,4.6-28.9,12c-7.4,7.4-12,17.6-12,28.9c0,11.3,4.6,21.5,12,28.9c7.4,7.4,17.6,12,28.9,12c11.3,0,21.5-4.6,28.9-12c7.4-7.4,12-17.6,12-28.9C217.1,82.4,212.5,72.2,205.1,64.8z"/></g></g>
|
|
7
|
+
</svg>
|
|
@@ -3,7 +3,6 @@ import { Slider } from '@miblanchard/react-native-slider';
|
|
|
3
3
|
|
|
4
4
|
import { View } from 'react-native';
|
|
5
5
|
import styles from './SliderRangeTemplateStyles';
|
|
6
|
-
import Text from '../Text';
|
|
7
6
|
import { Colors } from '../../configs';
|
|
8
7
|
import { useConfigGlobalState } from '../../iot/states';
|
|
9
8
|
import { DEVICE_TYPE } from '../../configs/Constants';
|
|
@@ -11,7 +10,7 @@ import _TextInput from '../Form/TextInput';
|
|
|
11
10
|
|
|
12
11
|
const SliderRangeTemplate = memo(
|
|
13
12
|
({ item, doAction, sensor, isWidgetOrder }) => {
|
|
14
|
-
const { configuration
|
|
13
|
+
const { configuration } = item;
|
|
15
14
|
const [configValues] = useConfigGlobalState('configValues');
|
|
16
15
|
const { config, min_value, max_value, action_data } = configuration;
|
|
17
16
|
const [value, setValue] = useState();
|
|
@@ -84,7 +83,6 @@ const SliderRangeTemplate = memo(
|
|
|
84
83
|
<View
|
|
85
84
|
style={(isWidgetOrder && styles.wrapOrderItem) || styles.viewBrightness}
|
|
86
85
|
>
|
|
87
|
-
<Text type="H4">{label}</Text>
|
|
88
86
|
<View style={styles.wrap}>
|
|
89
87
|
<Slider
|
|
90
88
|
step={1}
|
|
@@ -47,7 +47,7 @@ const useNewMessage = (
|
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
const TerminalBoxTemplate = ({ item, doAction, isWidgetOrder }) => {
|
|
50
|
-
const {
|
|
50
|
+
const { configuration } = item;
|
|
51
51
|
const { action_data, from_config, to_config } = configuration;
|
|
52
52
|
const [configValues] = useConfigGlobalState('configValues');
|
|
53
53
|
const [value, setValue] = useState();
|
|
@@ -140,9 +140,6 @@ const TerminalBoxTemplate = ({ item, doAction, isWidgetOrder }) => {
|
|
|
140
140
|
);
|
|
141
141
|
return (
|
|
142
142
|
<View style={(isWidgetOrder && styles.wrapOrderItem) || styles.wrap}>
|
|
143
|
-
<View>
|
|
144
|
-
<Text type="H4">{label}</Text>
|
|
145
|
-
</View>
|
|
146
143
|
<ScrollView
|
|
147
144
|
ref={scrollViewRef}
|
|
148
145
|
onContentSizeChange={() =>
|
|
@@ -15,7 +15,7 @@ import useKeyboardAnimated from '../../hooks/Explore/useKeyboardAnimated';
|
|
|
15
15
|
const TextBoxTemplate = ({ item, doAction, isWidgetOrder }) => {
|
|
16
16
|
const t = useTranslations();
|
|
17
17
|
const transY = useKeyboardAnimated();
|
|
18
|
-
const {
|
|
18
|
+
const { configuration } = item;
|
|
19
19
|
const { action_data, config } = configuration;
|
|
20
20
|
const [configValues] = useConfigGlobalState('configValues');
|
|
21
21
|
const [value, setValue] = useState();
|
|
@@ -35,10 +35,6 @@ const TextBoxTemplate = ({ item, doAction, isWidgetOrder }) => {
|
|
|
35
35
|
|
|
36
36
|
return (
|
|
37
37
|
<View style={(isWidgetOrder && styles.wrapOrderItem) || styles.wrap}>
|
|
38
|
-
<View>
|
|
39
|
-
<Text type="H4">{label}</Text>
|
|
40
|
-
</View>
|
|
41
|
-
|
|
42
38
|
<View style={styles.iconAndText}>
|
|
43
39
|
<Text style={styles.textValue} type="H4">
|
|
44
40
|
{valueText}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from 'react-native';
|
|
8
8
|
import DateTimePickerModal from 'react-native-modal-datetime-picker';
|
|
9
9
|
import renderer, { act } from 'react-test-renderer';
|
|
10
|
+
import WebView from 'react-native-webview';
|
|
10
11
|
|
|
11
12
|
import { Slider } from '@miblanchard/react-native-slider';
|
|
12
13
|
import ActionGroup from '..';
|
|
@@ -606,4 +607,54 @@ describe('Test ActionGroup', () => {
|
|
|
606
607
|
const elements = instance.findAllByType(SwitchButtonTemplate);
|
|
607
608
|
expect(elements).toHaveLength(1);
|
|
608
609
|
});
|
|
610
|
+
|
|
611
|
+
it('test render IframeWithConfig', async () => {
|
|
612
|
+
const actionItem = {
|
|
613
|
+
id: 1,
|
|
614
|
+
template: 'IFrameWithConfig',
|
|
615
|
+
label: 'LED',
|
|
616
|
+
configuration: {
|
|
617
|
+
url: 'http://localhost:3000/',
|
|
618
|
+
actions: [
|
|
619
|
+
{
|
|
620
|
+
action: 'action1-83cd1cf5d8f2',
|
|
621
|
+
name: 'Action 1',
|
|
622
|
+
action_data: {
|
|
623
|
+
id: 2,
|
|
624
|
+
name: 'Action 1',
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
],
|
|
628
|
+
},
|
|
629
|
+
};
|
|
630
|
+
const mockDoAction = jest.fn();
|
|
631
|
+
await act(async () => {
|
|
632
|
+
wrapper = renderer.create(
|
|
633
|
+
wrapComponent(actionItem, mockDoAction, sensor)
|
|
634
|
+
);
|
|
635
|
+
});
|
|
636
|
+
const instance = wrapper.root;
|
|
637
|
+
const webview = instance.findByType(WebView);
|
|
638
|
+
|
|
639
|
+
await act(async () => {
|
|
640
|
+
webview.props.onMessage({
|
|
641
|
+
nativeEvent: {
|
|
642
|
+
data: JSON.stringify({
|
|
643
|
+
type: 'triggerAction',
|
|
644
|
+
eraWidgetId: 1,
|
|
645
|
+
source: 'eraIframeWidget',
|
|
646
|
+
data: { actionKey: 'action1-83cd1cf5d8f2' },
|
|
647
|
+
}),
|
|
648
|
+
origin: 'http://localhost',
|
|
649
|
+
},
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
expect(mockDoAction).toHaveBeenCalledTimes(1);
|
|
653
|
+
expect(mockDoAction).toHaveBeenCalledWith(
|
|
654
|
+
actionItem.configuration.actions[0].action_data,
|
|
655
|
+
undefined,
|
|
656
|
+
false,
|
|
657
|
+
undefined
|
|
658
|
+
);
|
|
659
|
+
});
|
|
609
660
|
});
|
|
@@ -15,6 +15,8 @@ import TwoButtonTemplate from './TwoButtonTemplate';
|
|
|
15
15
|
import SwitchButtonTemplate from './OnOffTemplate/SwitchButtonTemplate';
|
|
16
16
|
import TextBoxTemplate from './TextBoxTemplate';
|
|
17
17
|
import TerminalBoxTemplate from './TerminalBoxTemplate';
|
|
18
|
+
import IFrameWithConfig from '../Widgets/IFrameWithConfig/IFrameWithConfig';
|
|
19
|
+
import { WIDGET_TYPE } from '../../configs/Constants';
|
|
18
20
|
|
|
19
21
|
export const getActionComponent = (template) => {
|
|
20
22
|
switch (template) {
|
|
@@ -52,6 +54,8 @@ export const getActionComponent = (template) => {
|
|
|
52
54
|
return TextBoxTemplate;
|
|
53
55
|
case 'TerminalBoxTemplate':
|
|
54
56
|
return TerminalBoxTemplate;
|
|
57
|
+
case WIDGET_TYPE.iframeWithConfig:
|
|
58
|
+
return IFrameWithConfig;
|
|
55
59
|
default:
|
|
56
60
|
return null;
|
|
57
61
|
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import MockAdapter from 'axios-mock-adapter';
|
|
2
|
+
import React, { useContext } from 'react';
|
|
3
|
+
import renderer, { act } from 'react-test-renderer';
|
|
4
|
+
|
|
5
|
+
import { Action } from '../../../../context/actionType';
|
|
6
|
+
import { API } from '../../../../configs';
|
|
7
|
+
import { AccessibilityLabel } from '../../../../configs/Constants';
|
|
8
|
+
import { SCProvider } from '../../../../context';
|
|
9
|
+
import { mockSCStore } from '../../../../context/mockStore';
|
|
10
|
+
import { Section } from '../../../Section';
|
|
11
|
+
import ItemDevice from '../../../Device/ItemDevice';
|
|
12
|
+
import { DeviceTemplate } from '../../../SubUnit/DeviceTemplate/DeviceTemplate';
|
|
13
|
+
import api from '../../../../utils/Apis/axios';
|
|
14
|
+
import Routes from '../../../../utils/Route';
|
|
15
|
+
import MyDashboardDevice from '..';
|
|
16
|
+
|
|
17
|
+
const mock = new MockAdapter(api.axiosInstance);
|
|
18
|
+
|
|
19
|
+
const wrapComponent = () => (
|
|
20
|
+
<SCProvider initState={mockSCStore({})}>
|
|
21
|
+
<MyDashboardDevice refreshing={true} />
|
|
22
|
+
</SCProvider>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
describe('Test MyDashboardDevice', () => {
|
|
26
|
+
let tree;
|
|
27
|
+
const data = [
|
|
28
|
+
{
|
|
29
|
+
id: 1,
|
|
30
|
+
name: 'EoH',
|
|
31
|
+
devices: [
|
|
32
|
+
{
|
|
33
|
+
id: 1,
|
|
34
|
+
name: 'LED',
|
|
35
|
+
is_managed_by_backend: true,
|
|
36
|
+
device_type: '',
|
|
37
|
+
station: {
|
|
38
|
+
id: 1,
|
|
39
|
+
name: 'Phòng họp',
|
|
40
|
+
},
|
|
41
|
+
quick_action: {
|
|
42
|
+
config_id: 1,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 2,
|
|
47
|
+
name: 'Presence',
|
|
48
|
+
is_managed_by_backend: true,
|
|
49
|
+
device_type: '',
|
|
50
|
+
station: {
|
|
51
|
+
id: 1,
|
|
52
|
+
name: 'Phòng họp',
|
|
53
|
+
},
|
|
54
|
+
station_items: [
|
|
55
|
+
{
|
|
56
|
+
configuration: {
|
|
57
|
+
config: 2,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 3,
|
|
64
|
+
name: 'Switch',
|
|
65
|
+
is_managed_by_backend: false,
|
|
66
|
+
device_type: 'GOOGLE_HOME',
|
|
67
|
+
station: {
|
|
68
|
+
id: 1,
|
|
69
|
+
name: 'Phòng họp',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
const mockSetAction = jest.fn();
|
|
76
|
+
|
|
77
|
+
beforeEach(async () => {
|
|
78
|
+
mock.reset();
|
|
79
|
+
useContext.mockReturnValue({
|
|
80
|
+
stateData: mockSCStore({
|
|
81
|
+
app: { isNeedUpdateCache: false },
|
|
82
|
+
}),
|
|
83
|
+
setAction: mockSetAction,
|
|
84
|
+
});
|
|
85
|
+
mockSetAction.mockClear();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('Test dashboard no device', async () => {
|
|
89
|
+
await act(async () => {
|
|
90
|
+
tree = await renderer.create(wrapComponent());
|
|
91
|
+
});
|
|
92
|
+
const instance = tree.root;
|
|
93
|
+
const sectors = instance.findAllByType(Section);
|
|
94
|
+
expect(sectors).toHaveLength(0);
|
|
95
|
+
expect(mockSetAction).not.toHaveBeenCalled();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('Test dashboard with devices and go to unit', async () => {
|
|
99
|
+
mock.onGet(API.UNIT.MY_DASHBOARD_DEVICES()).reply(200, data);
|
|
100
|
+
await act(async () => {
|
|
101
|
+
tree = await renderer.create(wrapComponent());
|
|
102
|
+
});
|
|
103
|
+
const instance = tree.root;
|
|
104
|
+
const devices = instance.findAllByType(ItemDevice);
|
|
105
|
+
expect(devices).toHaveLength(2);
|
|
106
|
+
const deviceTemplates = instance.findAllByType(DeviceTemplate);
|
|
107
|
+
expect(deviceTemplates).toHaveLength(1);
|
|
108
|
+
|
|
109
|
+
const goToUnit = instance.findByProps({
|
|
110
|
+
accessibilityLabel: `${AccessibilityLabel.UNIT_GO_TO_DETAIL}-0`,
|
|
111
|
+
});
|
|
112
|
+
await act(async () => {
|
|
113
|
+
await goToUnit.props.onPress();
|
|
114
|
+
});
|
|
115
|
+
expect(global.mockedNavigate).toHaveBeenCalledWith(Routes.UnitStack, {
|
|
116
|
+
params: {
|
|
117
|
+
unitId: 1,
|
|
118
|
+
},
|
|
119
|
+
screen: Routes.UnitDetail,
|
|
120
|
+
});
|
|
121
|
+
expect(mockSetAction).toHaveBeenCalledWith(
|
|
122
|
+
Action.SET_DEVICES_STATUS,
|
|
123
|
+
data[0].devices
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('Test dashboard with devices and go to device', async () => {
|
|
128
|
+
useContext.mockReturnValue({
|
|
129
|
+
stateData: mockSCStore({
|
|
130
|
+
app: { isNeedUpdateCache: true },
|
|
131
|
+
}),
|
|
132
|
+
setAction: mockSetAction,
|
|
133
|
+
});
|
|
134
|
+
mock.onGet(API.UNIT.MY_DASHBOARD_DEVICES()).reply(200, data);
|
|
135
|
+
await act(async () => {
|
|
136
|
+
tree = await renderer.create(wrapComponent());
|
|
137
|
+
});
|
|
138
|
+
const instance = tree.root;
|
|
139
|
+
const devices = instance.findAllByType(ItemDevice);
|
|
140
|
+
expect(devices).toHaveLength(2);
|
|
141
|
+
const deviceTemplates = instance.findAllByType(DeviceTemplate);
|
|
142
|
+
expect(deviceTemplates).toHaveLength(1);
|
|
143
|
+
|
|
144
|
+
await act(async () => {
|
|
145
|
+
await devices[0].props.goToDetail();
|
|
146
|
+
});
|
|
147
|
+
expect(global.mockedNavigate).toHaveBeenCalledWith(Routes.UnitStack, {
|
|
148
|
+
params: {
|
|
149
|
+
sensorId: 1,
|
|
150
|
+
station: {
|
|
151
|
+
id: 1,
|
|
152
|
+
name: 'Phòng họp',
|
|
153
|
+
},
|
|
154
|
+
title: 'LED',
|
|
155
|
+
unitId: 1,
|
|
156
|
+
},
|
|
157
|
+
screen: Routes.DeviceDetail,
|
|
158
|
+
});
|
|
159
|
+
expect(mockSetAction).toHaveBeenCalledTimes(2);
|
|
160
|
+
expect(mockSetAction).toHaveBeenNthCalledWith(
|
|
161
|
+
1,
|
|
162
|
+
Action.IS_CHECK_CLEAR_CACHE_UNITS,
|
|
163
|
+
false
|
|
164
|
+
);
|
|
165
|
+
expect(mockSetAction).toHaveBeenNthCalledWith(
|
|
166
|
+
2,
|
|
167
|
+
Action.SET_DEVICES_STATUS,
|
|
168
|
+
data[0].devices
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { API, Colors, Images } from '../../../configs';
|
|
2
|
+
import { AccessibilityLabel, DEVICE_TYPE } from '../../../configs/Constants';
|
|
3
|
+
import { Image, FlatList, TouchableOpacity, View } from 'react-native';
|
|
4
|
+
import React, {
|
|
5
|
+
memo,
|
|
6
|
+
useCallback,
|
|
7
|
+
useContext,
|
|
8
|
+
useMemo,
|
|
9
|
+
useEffect,
|
|
10
|
+
useState,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import { SCContext, useSCContextSelector } from '../../../context';
|
|
13
|
+
import { axiosGet, fetchWithCache } from '../../../utils/Apis/axios';
|
|
14
|
+
import { useWatchConfigs } from '../../../hooks/IoT';
|
|
15
|
+
import { useIsFocused, useNavigation } from '@react-navigation/native';
|
|
16
|
+
|
|
17
|
+
import { Action } from '../../../context/actionType';
|
|
18
|
+
import Routes from '../../../utils/Route';
|
|
19
|
+
import { Section } from '../../Section';
|
|
20
|
+
import Text from '../../Text';
|
|
21
|
+
import { keyExtractor } from '../../../utils/Utils';
|
|
22
|
+
import ItemDevice from '../../Device/ItemDevice';
|
|
23
|
+
import { DeviceTemplate } from '../../SubUnit/DeviceTemplate/DeviceTemplate';
|
|
24
|
+
import { useTranslations } from '../../../hooks/Common/useTranslations';
|
|
25
|
+
import styles from './styles';
|
|
26
|
+
|
|
27
|
+
const MyDashboardDevice = ({ refreshing }) => {
|
|
28
|
+
const t = useTranslations();
|
|
29
|
+
const isFocused = useIsFocused();
|
|
30
|
+
const navigation = useNavigation();
|
|
31
|
+
const { setAction } = useContext(SCContext);
|
|
32
|
+
const isNeedUpdateCache = useSCContextSelector(
|
|
33
|
+
(state) => state.app.isNeedUpdateCache
|
|
34
|
+
);
|
|
35
|
+
const [myDashboardDevices, setMyDashboardDevices] = useState([]);
|
|
36
|
+
|
|
37
|
+
const configsNeedWatching = useMemo(() => {
|
|
38
|
+
const configIds = [];
|
|
39
|
+
|
|
40
|
+
myDashboardDevices.forEach(({ devices }) => {
|
|
41
|
+
devices
|
|
42
|
+
.filter((device) => device.device_type !== DEVICE_TYPE.GOOGLE_HOME)
|
|
43
|
+
.forEach((device) => {
|
|
44
|
+
const quickActionId = device.quick_action?.config_id;
|
|
45
|
+
quickActionId && configIds.push(quickActionId);
|
|
46
|
+
|
|
47
|
+
device.station_items?.forEach((item) => {
|
|
48
|
+
const stationConfigId = item.configuration?.config;
|
|
49
|
+
stationConfigId && configIds.push(stationConfigId);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return configIds;
|
|
55
|
+
}, [myDashboardDevices]);
|
|
56
|
+
|
|
57
|
+
useWatchConfigs(configsNeedWatching);
|
|
58
|
+
|
|
59
|
+
const fetchMyDashboardDevices = useCallback(async () => {
|
|
60
|
+
if (isNeedUpdateCache) {
|
|
61
|
+
setAction(Action.IS_CHECK_CLEAR_CACHE_UNITS, false);
|
|
62
|
+
const { success, data } = await axiosGet(
|
|
63
|
+
API.UNIT.MY_DASHBOARD_DEVICES(),
|
|
64
|
+
{},
|
|
65
|
+
true
|
|
66
|
+
);
|
|
67
|
+
if (success) {
|
|
68
|
+
setMyDashboardDevices(data);
|
|
69
|
+
data.forEach((dashboard) => {
|
|
70
|
+
setAction(Action.SET_DEVICES_STATUS, dashboard.devices);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
await fetchWithCache(API.UNIT.MY_DASHBOARD_DEVICES(), {}, (response) => {
|
|
75
|
+
const { success, data } = response;
|
|
76
|
+
if (success) {
|
|
77
|
+
setMyDashboardDevices(data);
|
|
78
|
+
data.forEach((dashboard) => {
|
|
79
|
+
setAction(Action.SET_DEVICES_STATUS, dashboard.devices);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
87
|
+
const goToUnitDetail = useCallback(
|
|
88
|
+
(unit) => () => {
|
|
89
|
+
navigation.navigate(Routes.UnitStack, {
|
|
90
|
+
screen: Routes.UnitDetail,
|
|
91
|
+
params: {
|
|
92
|
+
unitId: unit.id,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
[navigation]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const goToDeviceDetail = useCallback(
|
|
100
|
+
(unitId, device) => () => {
|
|
101
|
+
navigation.navigate(Routes.UnitStack, {
|
|
102
|
+
screen: Routes.DeviceDetail,
|
|
103
|
+
params: {
|
|
104
|
+
unitId: unitId,
|
|
105
|
+
station: device.station,
|
|
106
|
+
sensorId: device.id,
|
|
107
|
+
title: device.name,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
},
|
|
111
|
+
[navigation]
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const renderStationItem = useCallback(
|
|
115
|
+
(device, unitId, stationItem) => {
|
|
116
|
+
return (
|
|
117
|
+
<DeviceTemplate
|
|
118
|
+
device={device}
|
|
119
|
+
stationItem={stationItem}
|
|
120
|
+
unit={{ id: unitId }}
|
|
121
|
+
station={device.station}
|
|
122
|
+
goToDetail={goToDeviceDetail(unitId, device)}
|
|
123
|
+
key={`device-template-${stationItem.id}`}
|
|
124
|
+
/>
|
|
125
|
+
);
|
|
126
|
+
},
|
|
127
|
+
[goToDeviceDetail]
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const renderDisplayItem = useCallback(
|
|
131
|
+
(device, unitId) => {
|
|
132
|
+
let displays = [];
|
|
133
|
+
if (!device.station_items?.length || device.quick_action) {
|
|
134
|
+
displays.push(
|
|
135
|
+
<ItemDevice
|
|
136
|
+
description={device.value}
|
|
137
|
+
title={device.name}
|
|
138
|
+
sensor={device}
|
|
139
|
+
unit={{ id: unitId }}
|
|
140
|
+
station={device.station}
|
|
141
|
+
goToDetail={goToDeviceDetail(unitId, device)}
|
|
142
|
+
key={`device-${device.id}`}
|
|
143
|
+
/>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (device.station_items?.length) {
|
|
147
|
+
displays.push(
|
|
148
|
+
...device.station_items.map((item) =>
|
|
149
|
+
renderStationItem(device, unitId, item)
|
|
150
|
+
)
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
return displays;
|
|
154
|
+
},
|
|
155
|
+
[goToDeviceDetail, renderStationItem]
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const renderItem = useCallback(
|
|
159
|
+
({ item, index }) => {
|
|
160
|
+
return (
|
|
161
|
+
<View style={styles.wrapItem}>
|
|
162
|
+
<View style={styles.titleItem}>
|
|
163
|
+
<Text type="H3" semibold>
|
|
164
|
+
{item.name}
|
|
165
|
+
</Text>
|
|
166
|
+
<TouchableOpacity
|
|
167
|
+
onPress={goToUnitDetail(item)}
|
|
168
|
+
style={styles.arrowRightButton}
|
|
169
|
+
accessibilityLabel={`${AccessibilityLabel.UNIT_GO_TO_DETAIL}-${index}`}
|
|
170
|
+
>
|
|
171
|
+
<Image source={Images.arrowBack} style={styles.arrowRight} />
|
|
172
|
+
</TouchableOpacity>
|
|
173
|
+
</View>
|
|
174
|
+
<View style={styles.wrapperDevices}>
|
|
175
|
+
{item.devices.map((device) => {
|
|
176
|
+
return renderDisplayItem(device, item.id);
|
|
177
|
+
})}
|
|
178
|
+
</View>
|
|
179
|
+
</View>
|
|
180
|
+
);
|
|
181
|
+
},
|
|
182
|
+
[goToUnitDetail, renderDisplayItem]
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (isFocused || refreshing) {
|
|
187
|
+
fetchMyDashboardDevices();
|
|
188
|
+
}
|
|
189
|
+
}, [fetchMyDashboardDevices, isFocused, refreshing]);
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<>
|
|
193
|
+
{!!myDashboardDevices.length && (
|
|
194
|
+
<Section style={styles.wrapperDashboardDevice}>
|
|
195
|
+
<Text
|
|
196
|
+
style={styles.dashboardDevicesTitle}
|
|
197
|
+
type="H5"
|
|
198
|
+
color={Colors.Gray8}
|
|
199
|
+
>
|
|
200
|
+
{t('dashboard_devices')}
|
|
201
|
+
</Text>
|
|
202
|
+
<View style={styles.container}>
|
|
203
|
+
<FlatList
|
|
204
|
+
keyExtractor={keyExtractor}
|
|
205
|
+
data={myDashboardDevices}
|
|
206
|
+
extraData={myDashboardDevices}
|
|
207
|
+
renderItem={renderItem}
|
|
208
|
+
contentContainerStyle={styles.contentContainerStyle}
|
|
209
|
+
refreshing={false}
|
|
210
|
+
/>
|
|
211
|
+
</View>
|
|
212
|
+
</Section>
|
|
213
|
+
)}
|
|
214
|
+
</>
|
|
215
|
+
);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export default memo(MyDashboardDevice);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import { Colors } from '../../../configs';
|
|
3
|
+
|
|
4
|
+
const styles = StyleSheet.create({
|
|
5
|
+
contentContainerStyle: {
|
|
6
|
+
paddingLeft: 16,
|
|
7
|
+
paddingRight: 16,
|
|
8
|
+
},
|
|
9
|
+
wrapperDashboardDevice: {
|
|
10
|
+
padding: 0,
|
|
11
|
+
paddingBottom: 10,
|
|
12
|
+
backgroundColor: Colors.White,
|
|
13
|
+
},
|
|
14
|
+
dashboardDevicesTitle: {
|
|
15
|
+
flexDirection: 'row',
|
|
16
|
+
justifyContent: 'space-between',
|
|
17
|
+
alignItems: 'center',
|
|
18
|
+
paddingHorizontal: 16,
|
|
19
|
+
paddingBottom: 8,
|
|
20
|
+
paddingTop: 16,
|
|
21
|
+
},
|
|
22
|
+
container: {
|
|
23
|
+
flex: 1,
|
|
24
|
+
backgroundColor: Colors.White,
|
|
25
|
+
},
|
|
26
|
+
wrapperDevices: {
|
|
27
|
+
flexWrap: 'wrap',
|
|
28
|
+
flexDirection: 'row',
|
|
29
|
+
justifyContent: 'space-between',
|
|
30
|
+
marginTop: 10,
|
|
31
|
+
borderBottomColor: Colors.Gray4,
|
|
32
|
+
borderBottomWidth: 1,
|
|
33
|
+
},
|
|
34
|
+
arrowRightButton: {
|
|
35
|
+
width: 20,
|
|
36
|
+
height: 20,
|
|
37
|
+
justifyContent: 'center',
|
|
38
|
+
alignItems: 'center',
|
|
39
|
+
},
|
|
40
|
+
arrowRight: {
|
|
41
|
+
transform: [
|
|
42
|
+
{
|
|
43
|
+
rotate: '180deg',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
tintColor: Colors.Gray7,
|
|
47
|
+
width: 8,
|
|
48
|
+
height: 12,
|
|
49
|
+
},
|
|
50
|
+
wrapItem: {
|
|
51
|
+
marginBottom: 16,
|
|
52
|
+
},
|
|
53
|
+
titleItem: {
|
|
54
|
+
flexDirection: 'row',
|
|
55
|
+
justifyContent: 'space-between',
|
|
56
|
+
alignItems: 'center',
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export default styles;
|
|
@@ -17,8 +17,6 @@ import { AccessibilityLabel } from '../../../configs/Constants';
|
|
|
17
17
|
import { useTranslations } from '../../../hooks/Common/useTranslations';
|
|
18
18
|
import SharedUnit from '../../Unit/SharedUnit';
|
|
19
19
|
import { axiosGet, fetchWithCache } from '../../../utils/Apis/axios';
|
|
20
|
-
import { preloadImagesFromUnits } from '../../../utils/Functions/preloadImages';
|
|
21
|
-
import { STORAGE_KEY } from '../../../utils/Storage';
|
|
22
20
|
import { SCContext, useSCContextSelector } from '../../../context';
|
|
23
21
|
import { Action } from '../../../context/actionType';
|
|
24
22
|
|
|
@@ -63,14 +61,6 @@ const MyPinnedSharedUnit = ({ refreshing }) => {
|
|
|
63
61
|
}
|
|
64
62
|
}, [fetchSharedUnitDashboard, isFocused, refreshing]);
|
|
65
63
|
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
sharedUnits?.length &&
|
|
68
|
-
preloadImagesFromUnits(
|
|
69
|
-
sharedUnits,
|
|
70
|
-
STORAGE_KEY.IS_FIRST_TIME_LOAD_MY_SHARE_UNIT
|
|
71
|
-
);
|
|
72
|
-
}, [sharedUnits]);
|
|
73
|
-
|
|
74
64
|
return (
|
|
75
65
|
<>
|
|
76
66
|
<Section>
|