@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.
Files changed (43) hide show
  1. package/package.json +1 -1
  2. package/src/Images/Common/search-menu.svg +7 -0
  3. package/src/commons/ActionGroup/OnOffTemplate/OnOffButtonTemplateStyle.js +2 -1
  4. package/src/commons/ActionGroup/OneBigButtonTemplateStyle.js +1 -2
  5. package/src/commons/ActionGroup/SliderRangeTemplate.js +1 -3
  6. package/src/commons/ActionGroup/TerminalBoxTemplate.js +1 -4
  7. package/src/commons/ActionGroup/TextBoxTemplate.js +1 -5
  8. package/src/commons/ActionGroup/ThreeButtonTemplate/components/ThreeButtonDefaultStyles.js +1 -1
  9. package/src/commons/ActionGroup/__test__/index.test.js +51 -0
  10. package/src/commons/ActionGroup/index.js +4 -0
  11. package/src/commons/Dashboard/MyPinnedSharedUnit/index.js +0 -10
  12. package/src/commons/Dashboard/MyUnit/__test__/MyUnit.test.js +114 -48
  13. package/src/commons/Dashboard/MyUnit/index.js +74 -27
  14. package/src/commons/Dashboard/MyUnit/styles.js +16 -1
  15. package/src/commons/DateTimeRangeChange/index.js +1 -1
  16. package/src/commons/SelectUnit/index.js +19 -5
  17. package/src/commons/SelectUnit/styles.js +0 -1
  18. package/src/commons/Widgets/IFrame/IFrameStyles.js +1 -0
  19. package/src/commons/Widgets/IFrameWithConfig/IFrameWithConfigStyles.js +1 -0
  20. package/src/configs/API.js +4 -0
  21. package/src/configs/Constants.js +3 -0
  22. package/src/context/reducer.ts +0 -5
  23. package/src/navigations/UnitStack.js +8 -0
  24. package/src/screens/ActivityLog/FilterPopup.js +4 -79
  25. package/src/screens/ActivityLog/__test__/FilterPopup.test.js +2 -6
  26. package/src/screens/ActivityLog/__test__/index.test.js +51 -29
  27. package/src/screens/ActivityLog/index.js +0 -1
  28. package/src/screens/ActivityLog/styles/filterPopupStyles.js +5 -2
  29. package/src/screens/Device/__test__/sensorDisplayItem.test.js +27 -0
  30. package/src/screens/Device/components/DonutChart.js +5 -14
  31. package/src/screens/Device/components/SensorDisplayItem.js +107 -74
  32. package/src/screens/Device/components/VisualChart.js +0 -12
  33. package/src/screens/Device/styles.js +11 -0
  34. package/src/screens/SubUnit/AddSubUnit.js +18 -8
  35. package/src/screens/SubUnit/__test__/AddSubUnit.test.js +5 -3
  36. package/src/screens/Unit/GoToDetailUnit.js +30 -0
  37. package/src/screens/Unit/__test__/GoToDetailUnit.test.js +103 -0
  38. package/src/utils/I18n/translations/en.js +1 -0
  39. package/src/utils/I18n/translations/vi.js +1 -0
  40. package/src/utils/Route/index.js +1 -0
  41. package/src/utils/Storage.js +0 -2
  42. package/src/utils/Utils.js +2 -1
  43. 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, label } = item;
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 IFrameWithConfig from '../../../commons/Widgets/IFrameWithConfig/IFrameWithConfig';
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={[styles.mediaContainer, isWidgetOrder && styles.cameraOrder]}
139
- >
140
- <MediaPlayerDetail
141
- uri={uri}
142
- thumbnail={{
143
- uri: background,
144
- }}
145
- key={`camera-device-${id}`}
146
- cameraName={name}
147
- width={standardizeWidth}
148
- height={standardizeHeight}
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
- <ActionGroup
174
- accessibilityLabel={AccessibilityLabel.DEVICE_DETAIL_ACTION_GROUP}
175
- doAction={doAction}
176
- sensor={sensor}
177
- id={idTemplate}
178
- item={item}
179
- isWidgetOrder={isWidgetOrder}
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
- <IFrame
185
- doAction={doAction}
186
- sensor={sensor}
187
- id={idTemplate}
188
- item={item}
189
- isWidgetOrder={isWidgetOrder}
190
- />
191
- );
192
- case WIDGET_TYPE.iframeWithConfig:
193
- return (
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
- <VisualChart
205
- item={item}
206
- sensor={sensor}
207
- isWidgetOrder={isWidgetOrder}
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
- <CurrentRainSensor
213
- size={120}
214
- textType="H4"
215
- iconSize={25}
216
- data={getDataCircleMini(item)}
217
- isWidgetOrder={isWidgetOrder}
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
- <LabelValue
224
- data={getDataCircleMini(item)}
225
- item={item}
226
- isWidgetOrder={isWidgetOrder}
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
- <CurrentRainSensor
234
- data={getData(item)}
235
- isWidgetOrder={isWidgetOrder}
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
- <PMSensorIndicator
241
- data={getData(item)}
242
- style={styles.simpleList}
243
- isWidgetOrder={isWidgetOrder}
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 <Anemometer data={getData(item)} item={item} />;
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
- <ProgressBar
251
- data={getData(item)}
252
- item={item}
253
- isWidgetOrder={isWidgetOrder}
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 <Compass data={getData(item)} isWidgetOrder={isWidgetOrder} />;
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 <DonutCharts item={item} isWidgetOrder={isWidgetOrder} />;
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 { axiosPost, createFormData } from '../../utils/Apis/axios';
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 unitChoose = useMemo(() => {
48
- return myUnits?.find((item) => item?.id === unit?.id);
49
- }, [myUnits, unit?.id]);
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
- unitChoose?.stations?.length >= permissions?.max_stations_per_unit
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
- unitChoose?.stations?.length,
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',
@@ -49,6 +49,7 @@ const Routes = {
49
49
  Dashboard: 'Dashboard',
50
50
  Shared: 'Shared',
51
51
  UnitDetail: 'UnitDetail',
52
+ GoToDetailUnit: 'GoToDetailUnit',
52
53
  DrawerMain: 'DrawerMain',
53
54
  ConnectDevices: 'ConnectDevices',
54
55
  SharedStack: 'SharedStack',
@@ -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) => {
@@ -231,7 +231,8 @@ export const PIN_MAPPING = {
231
231
  },
232
232
  };
233
233
 
234
- export const keyExtractor = (item, index) => (item?.id || index).toString();
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
- };