@eohjsc/react-native-smart-city 0.7.25 → 0.7.27
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/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/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/SelectUnit/index.js +19 -5
- package/src/commons/SelectUnit/styles.js +0 -1
- package/src/commons/Widgets/IFrame/IFrameStyles.js +1 -0
- package/src/commons/Widgets/IFrameWithConfig/IFrameWithConfigStyles.js +1 -0
- package/src/configs/API.js +4 -0
- package/src/configs/Constants.js +3 -0
- package/src/context/reducer.ts +0 -5
- package/src/navigations/UnitStack.js +8 -0
- package/src/screens/ActivityLog/FilterPopup.js +4 -79
- package/src/screens/ActivityLog/__test__/FilterPopup.test.js +2 -6
- package/src/screens/ActivityLog/__test__/index.test.js +51 -29
- package/src/screens/ActivityLog/index.js +0 -1
- package/src/screens/ActivityLog/styles/filterPopupStyles.js +5 -2
- 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 +107 -74
- package/src/screens/Device/components/VisualChart.js +0 -12
- package/src/screens/Device/styles.js +11 -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/I18n/translations/en.js +1 -0
- package/src/utils/I18n/translations/vi.js +1 -0
- package/src/utils/Route/index.js +1 -0
- package/src/utils/Storage.js +0 -2
- package/src/utils/Utils.js +2 -1
- package/src/utils/Functions/preloadImages.js +0 -38
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import { StyleSheet, View } from 'react-native';
|
|
3
3
|
import { Colors } from '../../../configs';
|
|
4
|
-
import Text from '../../../commons/Text';
|
|
5
4
|
import Highcharts from '../../../commons/Highcharts';
|
|
6
5
|
import { useConfigGlobalState } from '../../../iot/states';
|
|
7
6
|
|
|
@@ -63,7 +62,7 @@ const chartOptions = {
|
|
|
63
62
|
};
|
|
64
63
|
|
|
65
64
|
const DonutCharts = ({ item, isWidgetOrder }) => {
|
|
66
|
-
const { configuration
|
|
65
|
+
const { configuration } = item;
|
|
67
66
|
const { configs = [] } = configuration;
|
|
68
67
|
const [configValues] = useConfigGlobalState('configValues');
|
|
69
68
|
const [highchartsWebviewRef, setHighchartsWebviewRef] = useState(null);
|
|
@@ -92,13 +91,7 @@ const DonutCharts = ({ item, isWidgetOrder }) => {
|
|
|
92
91
|
}, [configs, getConfigValue, highchartsWebviewRef]);
|
|
93
92
|
|
|
94
93
|
return (
|
|
95
|
-
<View>
|
|
96
|
-
<View style={styles.titleHistory}>
|
|
97
|
-
<Text type="H3" semibold color={Colors.Gray9}>
|
|
98
|
-
{label}
|
|
99
|
-
</Text>
|
|
100
|
-
</View>
|
|
101
|
-
|
|
94
|
+
<View style={styles.wrapDonut}>
|
|
102
95
|
<Highcharts
|
|
103
96
|
setRef={setHighchartsWebviewRef}
|
|
104
97
|
options={chartOptions}
|
|
@@ -113,15 +106,13 @@ const DonutCharts = ({ item, isWidgetOrder }) => {
|
|
|
113
106
|
};
|
|
114
107
|
|
|
115
108
|
const styles = StyleSheet.create({
|
|
109
|
+
wrapDonut: {
|
|
110
|
+
marginBottom: 16,
|
|
111
|
+
},
|
|
116
112
|
chartStyle: {
|
|
117
113
|
backgroundColor: Colors.White,
|
|
118
114
|
flex: 1,
|
|
119
115
|
},
|
|
120
|
-
titleHistory: {
|
|
121
|
-
flexDirection: 'row',
|
|
122
|
-
justifyContent: 'space-between',
|
|
123
|
-
paddingHorizontal: 16,
|
|
124
|
-
},
|
|
125
116
|
webviewStyle: {
|
|
126
117
|
flex: 1,
|
|
127
118
|
minHeight: 200,
|
|
@@ -15,7 +15,7 @@ import Compass from '../../../commons/Device/WindDirection/Compass';
|
|
|
15
15
|
import Anemometer from '../../../commons/Device/WindSpeed/Anemometer';
|
|
16
16
|
import MediaPlayerDetail from '../../../commons/MediaPlayerDetail';
|
|
17
17
|
import IFrame from '../../../commons/Widgets/IFrame/IFrame';
|
|
18
|
-
import
|
|
18
|
+
import Text from '../../../commons/Text';
|
|
19
19
|
import { Device } from '../../../configs';
|
|
20
20
|
import { AccessibilityLabel, WIDGET_TYPE } from '../../../configs/Constants';
|
|
21
21
|
import { useSCContextSelector } from '../../../context';
|
|
@@ -44,7 +44,7 @@ export const SensorDisplayItem = ({
|
|
|
44
44
|
isWidgetOrder,
|
|
45
45
|
}) => {
|
|
46
46
|
const userId = useSCContextSelector((state) => state.auth.account.user.id);
|
|
47
|
-
const { configuration = {}, id: idTemplate } = item;
|
|
47
|
+
const { configuration = {}, id: idTemplate, label } = item;
|
|
48
48
|
const { type, template, uri, id, name, title, value_evaluation } =
|
|
49
49
|
configuration;
|
|
50
50
|
|
|
@@ -134,19 +134,22 @@ export const SensorDisplayItem = ({
|
|
|
134
134
|
switch (item.template) {
|
|
135
135
|
case 'camera':
|
|
136
136
|
return (
|
|
137
|
-
<View
|
|
138
|
-
style={
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
uri
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
137
|
+
<View>
|
|
138
|
+
<Text style={styles.widgetLabel}>{label}</Text>
|
|
139
|
+
<View
|
|
140
|
+
style={[styles.mediaContainer, isWidgetOrder && styles.cameraOrder]}
|
|
141
|
+
>
|
|
142
|
+
<MediaPlayerDetail
|
|
143
|
+
uri={uri}
|
|
144
|
+
thumbnail={{
|
|
145
|
+
uri: background,
|
|
146
|
+
}}
|
|
147
|
+
key={`camera-device-${id}`}
|
|
148
|
+
cameraName={name}
|
|
149
|
+
width={standardizeWidth}
|
|
150
|
+
height={standardizeHeight}
|
|
151
|
+
/>
|
|
152
|
+
</View>
|
|
150
153
|
</View>
|
|
151
154
|
);
|
|
152
155
|
// change response format, todo Bang refactor after fix
|
|
@@ -169,92 +172,117 @@ export const SensorDisplayItem = ({
|
|
|
169
172
|
case 'switch_button_action_template':
|
|
170
173
|
case 'TextBoxTemplate':
|
|
171
174
|
case 'TerminalBoxTemplate':
|
|
175
|
+
case WIDGET_TYPE.iframeWithConfig:
|
|
172
176
|
return (
|
|
173
|
-
<
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
177
|
+
<View>
|
|
178
|
+
<Text style={styles.widgetLabel}>{label}</Text>
|
|
179
|
+
<ActionGroup
|
|
180
|
+
accessibilityLabel={AccessibilityLabel.DEVICE_DETAIL_ACTION_GROUP}
|
|
181
|
+
doAction={doAction}
|
|
182
|
+
sensor={sensor}
|
|
183
|
+
id={idTemplate}
|
|
184
|
+
item={item}
|
|
185
|
+
isWidgetOrder={isWidgetOrder}
|
|
186
|
+
/>
|
|
187
|
+
</View>
|
|
181
188
|
);
|
|
182
189
|
case WIDGET_TYPE.iframe:
|
|
183
190
|
return (
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
<IFrameWithConfig
|
|
195
|
-
doAction={doAction}
|
|
196
|
-
sensor={sensor}
|
|
197
|
-
id={idTemplate}
|
|
198
|
-
item={item}
|
|
199
|
-
isWidgetOrder={isWidgetOrder}
|
|
200
|
-
/>
|
|
191
|
+
<View>
|
|
192
|
+
<Text style={styles.widgetLabel}>{label}</Text>
|
|
193
|
+
<IFrame
|
|
194
|
+
doAction={doAction}
|
|
195
|
+
sensor={sensor}
|
|
196
|
+
id={idTemplate}
|
|
197
|
+
item={item}
|
|
198
|
+
isWidgetOrder={isWidgetOrder}
|
|
199
|
+
/>
|
|
200
|
+
</View>
|
|
201
201
|
);
|
|
202
202
|
case 'history':
|
|
203
203
|
return (
|
|
204
|
-
<
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
204
|
+
<View>
|
|
205
|
+
<Text style={styles.widgetLabelHistory}>{label}</Text>
|
|
206
|
+
<VisualChart
|
|
207
|
+
item={item}
|
|
208
|
+
sensor={sensor}
|
|
209
|
+
isWidgetOrder={isWidgetOrder}
|
|
210
|
+
/>
|
|
211
|
+
</View>
|
|
209
212
|
);
|
|
210
213
|
case 'circle_mini':
|
|
211
214
|
return (
|
|
212
|
-
<
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
215
|
+
<View>
|
|
216
|
+
<Text style={styles.widgetLabel}>{label}</Text>
|
|
217
|
+
<CurrentRainSensor
|
|
218
|
+
size={120}
|
|
219
|
+
textType="H4"
|
|
220
|
+
iconSize={25}
|
|
221
|
+
data={getDataCircleMini(item)}
|
|
222
|
+
isWidgetOrder={isWidgetOrder}
|
|
223
|
+
/>
|
|
224
|
+
</View>
|
|
219
225
|
);
|
|
220
226
|
// use the same method to get data for circle_mini
|
|
221
227
|
case 'LabelValue':
|
|
222
228
|
return (
|
|
223
|
-
<
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
229
|
+
<View>
|
|
230
|
+
<Text style={styles.widgetLabel}>{label}</Text>
|
|
231
|
+
<LabelValue
|
|
232
|
+
data={getDataCircleMini(item)}
|
|
233
|
+
item={item}
|
|
234
|
+
isWidgetOrder={isWidgetOrder}
|
|
235
|
+
/>
|
|
236
|
+
</View>
|
|
228
237
|
);
|
|
229
238
|
case 'value':
|
|
230
239
|
switch (type || template) {
|
|
231
240
|
case 'circle':
|
|
232
241
|
return (
|
|
233
|
-
<
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
242
|
+
<View>
|
|
243
|
+
<Text style={styles.widgetLabel}>{label}</Text>
|
|
244
|
+
<CurrentRainSensor
|
|
245
|
+
data={getData(item)}
|
|
246
|
+
isWidgetOrder={isWidgetOrder}
|
|
247
|
+
/>
|
|
248
|
+
</View>
|
|
237
249
|
);
|
|
238
250
|
case 'simple_list':
|
|
239
251
|
return (
|
|
240
|
-
<
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
252
|
+
<View>
|
|
253
|
+
<Text style={styles.widgetLabel}>{label}</Text>
|
|
254
|
+
<PMSensorIndicator
|
|
255
|
+
data={getData(item)}
|
|
256
|
+
style={styles.simpleList}
|
|
257
|
+
isWidgetOrder={isWidgetOrder}
|
|
258
|
+
/>
|
|
259
|
+
</View>
|
|
245
260
|
);
|
|
246
261
|
case 'gauge':
|
|
247
|
-
return
|
|
262
|
+
return (
|
|
263
|
+
<View>
|
|
264
|
+
<Text style={styles.widgetLabel}>{label}</Text>
|
|
265
|
+
<Anemometer data={getData(item)} item={item} />
|
|
266
|
+
</View>
|
|
267
|
+
);
|
|
248
268
|
case 'progress_bar':
|
|
249
269
|
return (
|
|
250
|
-
<
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
270
|
+
<View>
|
|
271
|
+
<Text style={styles.widgetLabel}>{label}</Text>
|
|
272
|
+
<ProgressBar
|
|
273
|
+
data={getData(item)}
|
|
274
|
+
item={item}
|
|
275
|
+
isWidgetOrder={isWidgetOrder}
|
|
276
|
+
/>
|
|
277
|
+
</View>
|
|
255
278
|
);
|
|
256
279
|
case 'compass':
|
|
257
|
-
return
|
|
280
|
+
return (
|
|
281
|
+
<View>
|
|
282
|
+
<Text style={styles.widgetLabel}>{label}</Text>
|
|
283
|
+
<Compass data={getData(item)} isWidgetOrder={isWidgetOrder} />
|
|
284
|
+
</View>
|
|
285
|
+
);
|
|
258
286
|
case 'alert_status':
|
|
259
287
|
return (
|
|
260
288
|
<DeviceAlertStatus
|
|
@@ -274,7 +302,12 @@ export const SensorDisplayItem = ({
|
|
|
274
302
|
/>
|
|
275
303
|
);
|
|
276
304
|
case 'donut_chart_race':
|
|
277
|
-
return
|
|
305
|
+
return (
|
|
306
|
+
<View>
|
|
307
|
+
<Text style={styles.widgetLabel}>{label}</Text>
|
|
308
|
+
<DonutCharts item={item} isWidgetOrder={isWidgetOrder} />
|
|
309
|
+
</View>
|
|
310
|
+
);
|
|
278
311
|
default:
|
|
279
312
|
return <ListQualityIndicator data={getData(item)} />;
|
|
280
313
|
}
|
|
@@ -13,7 +13,6 @@ import { useFetchConfigHistory } from '../../../commons/UnitSummary/ConfigHistor
|
|
|
13
13
|
import { StyleSheet, View } from 'react-native';
|
|
14
14
|
import { Colors } from '../../../configs';
|
|
15
15
|
import ChartAggregationOption from '../../../commons/ChartAggregationOption';
|
|
16
|
-
import Text from '../../../commons/Text';
|
|
17
16
|
import { CHART_GROUP_TYPE, CHART_TIME } from '../../../configs/Constants';
|
|
18
17
|
import { getDateRangeOfWeek } from '../../../utils/Utils';
|
|
19
18
|
import Highcharts from '../../../commons/Highcharts';
|
|
@@ -113,16 +112,10 @@ const styles = StyleSheet.create({
|
|
|
113
112
|
backgroundColor: Colors.White,
|
|
114
113
|
flex: 1,
|
|
115
114
|
},
|
|
116
|
-
titleHistory: {
|
|
117
|
-
flexDirection: 'row',
|
|
118
|
-
justifyContent: 'space-between',
|
|
119
|
-
paddingHorizontal: 16,
|
|
120
|
-
},
|
|
121
115
|
aggregationView: {
|
|
122
116
|
flexDirection: 'row',
|
|
123
117
|
justifyContent: 'space-between',
|
|
124
118
|
paddingHorizontal: 16,
|
|
125
|
-
paddingTop: 8,
|
|
126
119
|
alignSelf: 'flex-end',
|
|
127
120
|
},
|
|
128
121
|
webviewStyle: {
|
|
@@ -312,11 +305,6 @@ const VisualChart = ({ item, isDemo = false, isWidgetOrder }) => {
|
|
|
312
305
|
|
|
313
306
|
return (
|
|
314
307
|
<View style={styles.container}>
|
|
315
|
-
<View style={styles.titleHistory}>
|
|
316
|
-
<Text type="H3" semibold color={Colors.Gray9}>
|
|
317
|
-
{item.label}
|
|
318
|
-
</Text>
|
|
319
|
-
</View>
|
|
320
308
|
<View style={styles.aggregationView}>
|
|
321
309
|
{canChooseGroup && (
|
|
322
310
|
<ChartAggregationOption
|
|
@@ -99,4 +99,15 @@ export default StyleSheet.create({
|
|
|
99
99
|
marginLeft: 16,
|
|
100
100
|
marginBottom: 16,
|
|
101
101
|
},
|
|
102
|
+
widgetLabel: {
|
|
103
|
+
fontSize: 16,
|
|
104
|
+
color: Colors.Black,
|
|
105
|
+
marginHorizontal: 20,
|
|
106
|
+
marginBottom: 4,
|
|
107
|
+
},
|
|
108
|
+
widgetLabelHistory: {
|
|
109
|
+
fontSize: 16,
|
|
110
|
+
color: Colors.Black,
|
|
111
|
+
marginHorizontal: 20,
|
|
112
|
+
},
|
|
102
113
|
});
|
|
@@ -13,11 +13,14 @@ import _TextInput from '../../commons/Form/TextInput';
|
|
|
13
13
|
import Text from '../../commons/Text';
|
|
14
14
|
import { API, Colors } from '../../configs';
|
|
15
15
|
import { AccessibilityLabel } from '../../configs/Constants';
|
|
16
|
-
import { useSCContextSelector } from '../../context';
|
|
17
16
|
import useKeyboardShow from '../../hooks/Common/useKeyboardShow';
|
|
18
17
|
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
19
18
|
import { replace } from '../../navigations/utils';
|
|
20
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
axiosPost,
|
|
21
|
+
createFormData,
|
|
22
|
+
fetchWithCache,
|
|
23
|
+
} from '../../utils/Apis/axios';
|
|
21
24
|
import { useBackendPermission } from '../../utils/Permission/backend';
|
|
22
25
|
import Routes from '../../utils/Route';
|
|
23
26
|
import { ToastBottomHelper } from '../../utils/Utils';
|
|
@@ -29,7 +32,6 @@ const AddSubUnit = ({ route }) => {
|
|
|
29
32
|
const { dismissKeyboard } = useKeyboardShow();
|
|
30
33
|
const { navigate, goBack, dispatch } = useNavigation();
|
|
31
34
|
const permissions = useBackendPermission();
|
|
32
|
-
const myUnits = useSCContextSelector((state) => state.myUnits);
|
|
33
35
|
|
|
34
36
|
const {
|
|
35
37
|
unit,
|
|
@@ -42,11 +44,15 @@ const AddSubUnit = ({ route }) => {
|
|
|
42
44
|
const [wallpaper, setWallpaper] = useState('');
|
|
43
45
|
const [imageUrl, setImageUrl] = useState('');
|
|
44
46
|
const [showImagePicker, setShowImagePicker] = useState(false);
|
|
47
|
+
const [unitDetail, setUnitDetail] = useState({});
|
|
45
48
|
let awaitCreate = useRef(false);
|
|
46
49
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
const fetchUnitDetail = useCallback(async () => {
|
|
51
|
+
await fetchWithCache(API.UNIT.UNIT_DETAIL(unit?.id), {}, (response) => {
|
|
52
|
+
const { success, data } = response;
|
|
53
|
+
success && setUnitDetail(data);
|
|
54
|
+
});
|
|
55
|
+
}, [unit?.id]);
|
|
50
56
|
|
|
51
57
|
const cleanData = () => {
|
|
52
58
|
setRoomName('');
|
|
@@ -133,7 +139,7 @@ const AddSubUnit = ({ route }) => {
|
|
|
133
139
|
} else {
|
|
134
140
|
awaitCreate.current = false;
|
|
135
141
|
if (
|
|
136
|
-
|
|
142
|
+
unitDetail?.stations?.length >= permissions?.max_stations_per_unit
|
|
137
143
|
) {
|
|
138
144
|
ToastBottomHelper.error(
|
|
139
145
|
t('reach_max_stations_per_unit', {
|
|
@@ -159,7 +165,7 @@ const AddSubUnit = ({ route }) => {
|
|
|
159
165
|
goBack,
|
|
160
166
|
navigate,
|
|
161
167
|
route.params,
|
|
162
|
-
|
|
168
|
+
unitDetail?.stations?.length,
|
|
163
169
|
permissions?.max_stations_per_unit,
|
|
164
170
|
]);
|
|
165
171
|
|
|
@@ -174,6 +180,10 @@ const AddSubUnit = ({ route }) => {
|
|
|
174
180
|
}
|
|
175
181
|
}, [imageUrl]);
|
|
176
182
|
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
fetchUnitDetail();
|
|
185
|
+
}, [fetchUnitDetail]);
|
|
186
|
+
|
|
177
187
|
const onChangeRoomName = useCallback(
|
|
178
188
|
(value) => {
|
|
179
189
|
setRoomName(value);
|
|
@@ -295,6 +295,11 @@ describe('Test AddSubUnit', () => {
|
|
|
295
295
|
isInsideUnit: false,
|
|
296
296
|
};
|
|
297
297
|
const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
|
|
298
|
+
mock.onGet(API.UNIT.UNIT_DETAIL(1)).reply(200, {
|
|
299
|
+
id: 1,
|
|
300
|
+
name: 'unit 1',
|
|
301
|
+
stations: [{ id: 1, name: 'station' }],
|
|
302
|
+
});
|
|
298
303
|
mock.onPost(API.SUB_UNIT.CREATE_SUB_UNIT(1)).reply(400, {});
|
|
299
304
|
await act(async () => {
|
|
300
305
|
tree = await create(
|
|
@@ -308,9 +313,6 @@ describe('Test AddSubUnit', () => {
|
|
|
308
313
|
},
|
|
309
314
|
},
|
|
310
315
|
},
|
|
311
|
-
myUnits: [
|
|
312
|
-
{ id: 1, name: 'unit 1', stations: [{ id: 1, name: 'station' }] },
|
|
313
|
-
],
|
|
314
316
|
})
|
|
315
317
|
);
|
|
316
318
|
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useNavigation } from '@react-navigation/native';
|
|
2
|
+
import React, { useCallback } from 'react';
|
|
3
|
+
|
|
4
|
+
import Routes from '../../utils/Route';
|
|
5
|
+
import SelectUnit from '../../commons/SelectUnit';
|
|
6
|
+
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
7
|
+
|
|
8
|
+
const GoToDetailUnit = () => {
|
|
9
|
+
const t = useTranslations();
|
|
10
|
+
const navigation = useNavigation();
|
|
11
|
+
|
|
12
|
+
const goToDetail = useCallback(
|
|
13
|
+
(item) => {
|
|
14
|
+
navigation.navigate(Routes.UnitDetail, {
|
|
15
|
+
unitId: item.id,
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
[navigation]
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<SelectUnit
|
|
23
|
+
title={t('text_select_a_unit')}
|
|
24
|
+
subTitle={t('text_select_go_to_unit_detail')}
|
|
25
|
+
onPressNext={goToDetail}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default GoToDetailUnit;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useNavigation } from '@react-navigation/native';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import renderer, { act } from 'react-test-renderer';
|
|
4
|
+
import MockAdapter from 'axios-mock-adapter';
|
|
5
|
+
|
|
6
|
+
import { SCProvider } from '../../../context';
|
|
7
|
+
import { mockSCStore } from '../../../context/mockStore';
|
|
8
|
+
import GoToDetailUnit from '../GoToDetailUnit';
|
|
9
|
+
import { Search } from '../../../commons/DevMode';
|
|
10
|
+
import { AccessibilityLabel } from '../../../configs/Constants';
|
|
11
|
+
import Routes from '../../../utils/Route';
|
|
12
|
+
import api from '../../../utils/Apis/axios';
|
|
13
|
+
import { API } from '../../../configs';
|
|
14
|
+
|
|
15
|
+
const wrapComponent = () => (
|
|
16
|
+
<SCProvider initState={mockSCStore({})}>
|
|
17
|
+
<GoToDetailUnit />
|
|
18
|
+
</SCProvider>
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const mock = new MockAdapter(api.axiosInstance);
|
|
22
|
+
|
|
23
|
+
describe('Test GoToDetailUnit', () => {
|
|
24
|
+
let tree;
|
|
25
|
+
const data = [
|
|
26
|
+
{
|
|
27
|
+
id: 1,
|
|
28
|
+
name: 'Name 1',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 2,
|
|
32
|
+
name: 'Name 2',
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const mockedNavigate = useNavigation().navigate;
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
mockedNavigate.mockReset();
|
|
39
|
+
mock.resetHistory();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('test render and navigate', async () => {
|
|
43
|
+
mock.onGet(API.SHARE.UNITS()).reply(200, data);
|
|
44
|
+
await act(async () => {
|
|
45
|
+
tree = await renderer.create(wrapComponent());
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const instance = tree.root;
|
|
49
|
+
const unitItems = instance.findAllByProps({
|
|
50
|
+
accessibilityLabel: AccessibilityLabel.ITEM_UNIT,
|
|
51
|
+
});
|
|
52
|
+
expect(unitItems).toHaveLength(2);
|
|
53
|
+
const selectUnit = instance.findByProps({
|
|
54
|
+
accessibilityLabel: `${AccessibilityLabel.SELECT_UNIT_SELECT}-1`,
|
|
55
|
+
});
|
|
56
|
+
await act(async () => {
|
|
57
|
+
await selectUnit.props.onPress();
|
|
58
|
+
});
|
|
59
|
+
const rightClick = instance.findByProps({
|
|
60
|
+
accessibilityLabelPrefix: AccessibilityLabel.PREFIX.SELECT_UNIT,
|
|
61
|
+
});
|
|
62
|
+
await act(async () => {
|
|
63
|
+
await rightClick.props.onRightClick();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(global.mockedNavigate).toHaveBeenCalledWith(Routes.UnitDetail, {
|
|
67
|
+
unitId: 1,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('test filter unit by name', async () => {
|
|
72
|
+
mock.onGet(API.SHARE.UNITS()).reply(200, data);
|
|
73
|
+
await act(async () => {
|
|
74
|
+
tree = await renderer.create(wrapComponent());
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const instance = tree.root;
|
|
78
|
+
const search = instance.findByType(Search);
|
|
79
|
+
await act(() => {
|
|
80
|
+
search.props.onSearch('Name 2');
|
|
81
|
+
});
|
|
82
|
+
const unitItems = instance.findAllByProps({
|
|
83
|
+
accessibilityLabel: AccessibilityLabel.ITEM_UNIT,
|
|
84
|
+
});
|
|
85
|
+
expect(unitItems).toHaveLength(1);
|
|
86
|
+
const selectUnit = instance.findByProps({
|
|
87
|
+
accessibilityLabel: `${AccessibilityLabel.SELECT_UNIT_SELECT}-2`,
|
|
88
|
+
});
|
|
89
|
+
await act(async () => {
|
|
90
|
+
await selectUnit.props.onPress();
|
|
91
|
+
});
|
|
92
|
+
const rightClick = instance.findByProps({
|
|
93
|
+
accessibilityLabelPrefix: AccessibilityLabel.PREFIX.SELECT_UNIT,
|
|
94
|
+
});
|
|
95
|
+
await act(async () => {
|
|
96
|
+
await rightClick.props.onRightClick();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(global.mockedNavigate).toHaveBeenCalledWith(Routes.UnitDetail, {
|
|
100
|
+
unitId: 2,
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -382,6 +382,7 @@ export default {
|
|
|
382
382
|
select_a_sub_unit_want_add_device:
|
|
383
383
|
'Select a sub-unit that you want to add this device in',
|
|
384
384
|
text_select_a_unit_have_device: 'Select a unit that has this device',
|
|
385
|
+
text_select_go_to_unit_detail: 'Select a unit to view details',
|
|
385
386
|
text_select_permissions: 'Select permissions',
|
|
386
387
|
text_select_permissions_desc: 'Select permissions will be grant',
|
|
387
388
|
text_read_config: 'Read configs',
|
|
@@ -435,6 +435,7 @@ export default {
|
|
|
435
435
|
text_select_a_gateway: 'Chọn một gateway',
|
|
436
436
|
text_select_a_unit_desc: 'Chọn 1 địa điểm bạn muốn thêm thành viên',
|
|
437
437
|
text_select_a_unit_have_device: 'Chọn một địa điểm có thiết bị này',
|
|
438
|
+
text_select_go_to_unit_detail: 'Chọn địa điểm để xem chi tiết',
|
|
438
439
|
select_a_sub_unit_want_add_device:
|
|
439
440
|
'Chọn một đơn vị con mà bạn muốn thêm thiết bị này vào',
|
|
440
441
|
text_select_permissions: 'Chọn hành động',
|
package/src/utils/Route/index.js
CHANGED
package/src/utils/Storage.js
CHANGED
|
@@ -4,8 +4,6 @@ export const STORAGE_KEY = {
|
|
|
4
4
|
FIREBASE_APP_CONFIG: 'app_config',
|
|
5
5
|
FIREBASE_EXCHANGE_RATES: 'exchange_rates',
|
|
6
6
|
FIREBASE_REVIEW_COMMENT_VIDEO: 'review_comment_video',
|
|
7
|
-
IS_FIRST_TIME_LOAD_MY_UNITS: 'IS_FIRST_TIME_LOAD_MY_UNITS',
|
|
8
|
-
IS_FIRST_TIME_LOAD_MY_SHARE_UNIT: 'IS_FIRST_TIME_LOAD_MY_SHARE_UNIT',
|
|
9
7
|
};
|
|
10
8
|
|
|
11
9
|
export const storeData = async (key, value) => {
|
package/src/utils/Utils.js
CHANGED
|
@@ -231,7 +231,8 @@ export const PIN_MAPPING = {
|
|
|
231
231
|
},
|
|
232
232
|
};
|
|
233
233
|
|
|
234
|
-
export const keyExtractor = (item, index) =>
|
|
234
|
+
export const keyExtractor = (item, index) =>
|
|
235
|
+
item?.id ? `id-${item.id}` : `index-${index}`;
|
|
235
236
|
|
|
236
237
|
export default {
|
|
237
238
|
validateEmail,
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import FastImage from 'react-native-fast-image';
|
|
2
|
-
import { API } from '../../configs';
|
|
3
|
-
import { axiosGet } from '../Apis/axios';
|
|
4
|
-
import { getData, STORAGE_KEY, storeData } from '../Storage';
|
|
5
|
-
|
|
6
|
-
export const preloadImagesFromUnits = async (units, storeKey) => {
|
|
7
|
-
const isFirstTime = await getData(storeKey);
|
|
8
|
-
if (units?.length && !isFirstTime) {
|
|
9
|
-
await storeData(storeKey, 'true');
|
|
10
|
-
const results = await Promise.all(
|
|
11
|
-
units.map(async (item) => {
|
|
12
|
-
const unitId =
|
|
13
|
-
storeKey === STORAGE_KEY.IS_FIRST_TIME_LOAD_MY_UNITS
|
|
14
|
-
? item?.id
|
|
15
|
-
: item?.unit?.id;
|
|
16
|
-
if (unitId) {
|
|
17
|
-
const { success, data } = await axiosGet(
|
|
18
|
-
API.UNIT.UNIT_DETAIL(unitId),
|
|
19
|
-
{},
|
|
20
|
-
true
|
|
21
|
-
);
|
|
22
|
-
return success && data;
|
|
23
|
-
}
|
|
24
|
-
})
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
const icons = results
|
|
28
|
-
.map((item) => item?.stations?.map((i) => i?.background) || [])
|
|
29
|
-
.flat()
|
|
30
|
-
.filter(Boolean);
|
|
31
|
-
FastImage.preload(
|
|
32
|
-
icons.map((uri) => ({
|
|
33
|
-
uri,
|
|
34
|
-
priority: FastImage.priority.high,
|
|
35
|
-
}))
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
};
|