@eohjsc/react-native-smart-city 0.2.82 → 0.2.83

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 +3 -3
  2. package/src/commons/ActionGroup/CurtainButtonTemplate.js +32 -21
  3. package/src/commons/ActionGroup/NumberUpDownActionTemplate.js +8 -6
  4. package/src/commons/ActionGroup/OnOffTemplate/index.js +11 -3
  5. package/src/commons/ActionGroup/OneBigButtonTemplate.js +10 -7
  6. package/src/commons/ActionGroup/OptionsDropdownActionTemplate.js +5 -2
  7. package/src/commons/ActionGroup/StatesGridActionTemplate.js +7 -3
  8. package/src/commons/ActionGroup/ThreeButtonTemplate.js +33 -24
  9. package/src/commons/ActionGroup/__test__/OnOffTemplate.test.js +18 -6
  10. package/src/commons/ActionGroup/__test__/OneBigButtonTemplate.test.js +9 -1
  11. package/src/commons/ActionGroup/__test__/OptionsDropdownTemplate.test.js +25 -13
  12. package/src/commons/ActionGroup/__test__/index.test.js +48 -14
  13. package/src/commons/Device/HorizontalBarChart.js +7 -1
  14. package/src/commons/Device/ItemDevice.js +8 -5
  15. package/src/commons/EmergencyButton/AlertSendConfirm.js +2 -2
  16. package/src/commons/EmergencyButton/AlertSent.js +2 -2
  17. package/src/commons/SubUnit/Favorites/index.js +2 -0
  18. package/src/commons/SubUnit/ShortDetail.js +7 -1
  19. package/src/configs/API.js +2 -4
  20. package/src/configs/Constants.js +2 -0
  21. package/src/iot/RemoteControl/Internet.js +8 -1
  22. package/src/iot/RemoteControl/index.js +4 -2
  23. package/src/screens/Device/EditDevice/__test__/EditDevice.test.js +2 -2
  24. package/src/screens/Device/EditDevice/index.js +2 -2
  25. package/src/screens/Device/__test__/detail.test.js +18 -11
  26. package/src/screens/Device/components/SensorConnectStatusViewHeader.js +2 -2
  27. package/src/screens/Device/components/SensorDisplayItem.js +2 -2
  28. package/src/screens/Device/detail.js +58 -20
  29. package/src/screens/Notification/__test__/NotificationItem.test.js +186 -14
  30. package/src/screens/Notification/components/NotificationItem.js +128 -2
  31. package/src/screens/Notification/styles/NotificationItemStyles.js +3 -3
  32. package/src/screens/SubUnit/AddSubUnit.js +4 -1
  33. package/src/screens/SubUnit/__test__/AddSubUnit.test.js +148 -0
  34. package/src/screens/Unit/Detail.js +10 -0
  35. package/src/screens/Unit/Summaries.js +2 -2
  36. package/src/screens/Unit/__test__/CheckSendEmail.test.js +10 -0
  37. package/src/screens/Unit/__test__/Detail.test.js +10 -0
  38. package/src/screens/Unit/components/__test__/SharedUnit.test.js +21 -2
  39. package/src/screens/UnitSummary/__test__/index.test.js +3 -3
  40. package/src/screens/UnitSummary/components/RunningDevices/__test__/index.test.js +2 -2
  41. package/src/screens/UnitSummary/index.js +52 -9
  42. package/src/utils/I18n/translations/en.json +7 -0
  43. package/src/utils/I18n/translations/vi.json +7 -0
@@ -199,4 +199,152 @@ describe('Test AddSubUnit', () => {
199
199
  visibilityTime: 1000,
200
200
  });
201
201
  });
202
+
203
+ test('test create Unit', async () => {
204
+ const response = {
205
+ success: true,
206
+ status: 200,
207
+ data: {
208
+ id: 2,
209
+ },
210
+ };
211
+ route.params = {
212
+ ...route.params,
213
+ location: 'Unit address',
214
+ isAddUnit: true,
215
+ };
216
+
217
+ axios.post.mockImplementation(async () => {
218
+ return response;
219
+ });
220
+
221
+ await act(async () => {
222
+ tree = await create(wrapComponent(route));
223
+ });
224
+
225
+ const instance = tree.root;
226
+ const viewButtonBottom = await makeValidateData(instance);
227
+
228
+ await act(async () => {
229
+ await viewButtonBottom.props.onRightClick();
230
+ });
231
+ expect(axios.post).toHaveBeenCalled();
232
+ expect(mockedNavigate).toHaveBeenCalledWith(Routes.UnitStack, {
233
+ screen: Routes.UnitDetail,
234
+ params: {
235
+ unitId: response.data.id,
236
+ routeName: Routes.DashboardStack,
237
+ },
238
+ });
239
+ });
240
+
241
+ test('test choose Location', async () => {
242
+ route.params = {
243
+ ...route.params,
244
+ location: '',
245
+ isAddUnit: true,
246
+ };
247
+ await act(async () => {
248
+ tree = await create(wrapComponent(route));
249
+ });
250
+
251
+ const instance = tree.root;
252
+ const buttonChooseLocation = instance.find(
253
+ (el) => el.props.testID === TESTID.ADD_SUB_UNIT_BUTTON_CHOOSE_LOCATION
254
+ );
255
+ act(() => {
256
+ buttonChooseLocation.props.onPress();
257
+ });
258
+ expect(mockedNavigate).toHaveBeenCalledWith(Routes.AddLocationMaps);
259
+ });
260
+
261
+ test('test create Unit Fail', async () => {
262
+ const response = {
263
+ success: false,
264
+ status: 400,
265
+ };
266
+ route.params = {
267
+ ...route.params,
268
+ location: 'Unit address',
269
+ isAddUnit: true,
270
+ };
271
+
272
+ axios.post.mockImplementation(async () => {
273
+ return response;
274
+ });
275
+
276
+ await act(async () => {
277
+ tree = await create(wrapComponent(route));
278
+ });
279
+
280
+ const instance = tree.root;
281
+ const viewButtonBottom = await makeValidateData(instance);
282
+
283
+ await act(async () => {
284
+ await viewButtonBottom.props.onRightClick();
285
+ });
286
+ expect(axios.post).toHaveBeenCalled();
287
+ expect(Toast.show).toHaveBeenCalledWith({
288
+ type: 'error',
289
+ position: 'bottom',
290
+ text1: getTranslate('en', 'text_create_unit_fail'),
291
+ visibilityTime: 1000,
292
+ });
293
+ });
294
+
295
+ test('test create sub-unit type AddHassiDevice', async () => {
296
+ const response = {
297
+ status: 200,
298
+ success: true,
299
+ data: {},
300
+ };
301
+ route.params = {
302
+ ...route.params,
303
+ addType: 'AddHassiDevice',
304
+ };
305
+
306
+ axios.post.mockImplementation(async () => {
307
+ return response;
308
+ });
309
+
310
+ await act(async () => {
311
+ tree = await create(wrapComponent(route));
312
+ });
313
+ const instance = tree.root;
314
+ const viewButtonBottom = await makeValidateData(instance);
315
+ await act(async () => {
316
+ await viewButtonBottom.props.onRightClick();
317
+ });
318
+ expect(axios.post).toHaveBeenCalled();
319
+ expect(mockedGoBack).toHaveBeenCalled();
320
+ });
321
+
322
+ test('test create sub-unit type AddNewGateway', async () => {
323
+ const response = {
324
+ status: 200,
325
+ success: true,
326
+ data: {},
327
+ };
328
+ route.params = {
329
+ ...route.params,
330
+ addType: 'AddNewGateway',
331
+ };
332
+
333
+ axios.post.mockImplementation(async () => {
334
+ return response;
335
+ });
336
+
337
+ await act(async () => {
338
+ tree = await create(wrapComponent(route));
339
+ });
340
+ const instance = tree.root;
341
+ const viewButtonBottom = await makeValidateData(instance);
342
+ await act(async () => {
343
+ await viewButtonBottom.props.onRightClick();
344
+ });
345
+ expect(axios.post).toHaveBeenCalled();
346
+ expect(mockedNavigate).toHaveBeenCalledWith(Routes.AddCommonSelectSubUnit, {
347
+ ...route.params,
348
+ });
349
+ });
202
350
  });
@@ -2,6 +2,7 @@ import React, { useCallback, useContext, useEffect, useState } from 'react';
2
2
  import { AppState, RefreshControl, View } from 'react-native';
3
3
  import { useIsFocused } from '@react-navigation/native';
4
4
  import { useTranslations } from '../../hooks/Common/useTranslations';
5
+ import { useNetInfo } from '@react-native-community/netinfo';
5
6
 
6
7
  import styles from './styles';
7
8
  import AddMenu from './AddMenu';
@@ -62,6 +63,7 @@ const UnitDetail = ({ route }) => {
62
63
  automates: [],
63
64
  });
64
65
  const [isGGHomeConnected, setIsGGHomeConnected] = useState(false);
66
+ const [isNetworkConnected, setIsNetworkConnected] = useState(true);
65
67
  const [station, setStation] = useState({});
66
68
  const [indexStation, setIndexStation] = useState(0);
67
69
  const [showAdd, setShowAdd, setHideAdd] = useBoolean();
@@ -73,6 +75,8 @@ const UnitDetail = ({ route }) => {
73
75
 
74
76
  const { isOwner } = useIsOwnerOfUnit(unit.user_id);
75
77
 
78
+ const netInfo = useNetInfo();
79
+
76
80
  const handleFullScreen = (data) => {
77
81
  setIsFullScreen(!isFullScreen);
78
82
  setDataFullScreen(data);
@@ -175,6 +179,10 @@ const UnitDetail = ({ route }) => {
175
179
  };
176
180
  }, [handleAppStateChange]);
177
181
 
182
+ useEffect(() => {
183
+ setIsNetworkConnected(netInfo.isConnected);
184
+ }, [netInfo.isConnected]);
185
+
178
186
  const handleGoogleHomeConnect = useCallback(
179
187
  async (options) => {
180
188
  let isConnected = await googleHomeConnect(options); // this may wrong if have multiple connection
@@ -281,6 +289,7 @@ const UnitDetail = ({ route }) => {
281
289
  isOwner={isOwner}
282
290
  favorites={favorites}
283
291
  wrapItemStyle={styles.wrapItemStyle}
292
+ isNetworkConnected={isNetworkConnected}
284
293
  isGGHomeConnected={isGGHomeConnected}
285
294
  />
286
295
  );
@@ -307,6 +316,7 @@ const UnitDetail = ({ route }) => {
307
316
  <ShortDetailSubUnit
308
317
  unit={unit}
309
318
  station={station}
319
+ isNetworkConnected={isNetworkConnected}
310
320
  isGGHomeConnected={isGGHomeConnected}
311
321
  />
312
322
  );
@@ -34,8 +34,8 @@ const Summaries = memo(({ unit }) => {
34
34
  const goToSummary = useCallback(
35
35
  (summary) => {
36
36
  navigation.navigate(Routes.UnitSummary, {
37
- summary,
38
- unit,
37
+ summaryData: summary,
38
+ unitData: unit,
39
39
  });
40
40
  },
41
41
  [navigation, unit]
@@ -26,6 +26,16 @@ jest.mock('../../../iot/RemoteControl/GoogleHome', () => ({
26
26
 
27
27
  jest.mock('axios');
28
28
 
29
+ jest.mock('@react-native-community/netinfo', () => {
30
+ return {
31
+ useNetInfo: () => {
32
+ return {
33
+ isConnected: true,
34
+ };
35
+ },
36
+ };
37
+ });
38
+
29
39
  const wrapComponent = (route, unitData, account) => (
30
40
  <SCProvider initState={mockSCStore({})}>
31
41
  <UnitDetail
@@ -62,6 +62,16 @@ jest.mock('home-assistant-js-websocket', () => {
62
62
  };
63
63
  });
64
64
 
65
+ jest.mock('@react-native-community/netinfo', () => {
66
+ return {
67
+ useNetInfo: () => {
68
+ return {
69
+ isConnected: true,
70
+ };
71
+ },
72
+ };
73
+ });
74
+
65
75
  jest.mock('axios');
66
76
 
67
77
  describe('Test UnitDetail', () => {
@@ -12,10 +12,15 @@ import { mockSCStore } from '../../../../context/mockStore';
12
12
 
13
13
  jest.mock('axios');
14
14
 
15
+ const mockRenewItem = jest.fn();
15
16
  const wrapComponent = (item, navigation) => (
16
17
  <SCProvider initState={mockSCStore({})}>
17
- <SharedUnit item={item} navigation={navigation} />
18
- );
18
+ <SharedUnit
19
+ item={item}
20
+ navigation={navigation}
21
+ renewItem={mockRenewItem}
22
+ index={0}
23
+ />
19
24
  </SCProvider>
20
25
  );
21
26
 
@@ -55,6 +60,13 @@ describe('Test SharedUnit', () => {
55
60
 
56
61
  test('test create SharedUnit unit is not pin, not star', async () => {
57
62
  const navigation = useNavigation();
63
+ const response = {
64
+ status: 200,
65
+ success: true,
66
+ };
67
+ axios.post.mockImplementation(async () => {
68
+ return response;
69
+ });
58
70
 
59
71
  await act(async () => {
60
72
  tree = await create(wrapComponent(item, navigation));
@@ -100,6 +112,13 @@ describe('Test SharedUnit', () => {
100
112
  const navigation = useNavigation();
101
113
  item.is_pin = true;
102
114
  item.is_star = true;
115
+ const response = {
116
+ status: 200,
117
+ success: true,
118
+ };
119
+ axios.post.mockImplementation(async () => {
120
+ return response;
121
+ });
103
122
 
104
123
  await act(async () => {
105
124
  tree = await create(wrapComponent(item, navigation));
@@ -43,10 +43,10 @@ describe('Test UnitSummary', () => {
43
43
  Date.now = jest.fn(() => new Date('2021-01-24T12:00:00.000Z'));
44
44
  route = {
45
45
  params: {
46
- unit: {
46
+ unitData: {
47
47
  id: 1,
48
48
  },
49
- summary: {
49
+ summaryData: {
50
50
  id: 1,
51
51
  name: '',
52
52
  screen: Routes.AirQuality,
@@ -155,7 +155,7 @@ describe('Test UnitSummary', () => {
155
155
 
156
156
  list_value.forEach((value, index) => {
157
157
  test(`create Unit Summarty Detail ${value}`, async () => {
158
- route.params.summary.screen = value;
158
+ route.params.summaryData.screen = value;
159
159
  await act(async () => {
160
160
  tree = await create(wrapComponent(route));
161
161
  });
@@ -113,10 +113,10 @@ describe('test RunningDevices', () => {
113
113
  });
114
114
  expect(mockedNavigate).toBeCalledWith('DeviceDetail', {
115
115
  isGGHomeConnected: false,
116
- sensor: summaryDetail.devices[0],
116
+ sensorData: summaryDetail.devices[0],
117
117
  station: 'station',
118
118
  title: undefined,
119
- unit: unit,
119
+ unitData: unit,
120
120
  });
121
121
  });
122
122
  });
@@ -23,15 +23,17 @@ import { timeDifference } from '../../utils/Converter/time';
23
23
 
24
24
  const UnitSummary = memo(({ route }) => {
25
25
  const t = useTranslations();
26
- const { unit, summary } = route.params;
26
+ const { unitData, unitId, summaryData, summaryId } = route.params;
27
27
  const [summaryDetail, setSummaryDetail] = useState({});
28
+ const [unit, setUnit] = useState(unitData || { id: unitId });
29
+ const [summary, setSummary] = useState(summaryData || { id: summaryId });
28
30
 
29
31
  const [loading, setLoading] = useState(false);
30
32
  const [lastUpdated, setLastUpdated] = useState();
31
33
  const navigation = useNavigation();
32
34
 
33
35
  const getComponentAndGuide = useCallback(() => {
34
- switch (summary.screen) {
36
+ switch (summary?.screen) {
35
37
  case Routes.AirQuality:
36
38
  return {
37
39
  guideName: Routes.AQIGuide,
@@ -66,12 +68,17 @@ const UnitSummary = memo(({ route }) => {
66
68
  return {
67
69
  componentName: ThreePhasePowerConsumption,
68
70
  };
71
+ default:
72
+ return {
73
+ guideName: null,
74
+ componentName: null,
75
+ };
69
76
  }
70
- }, [summary.screen]);
77
+ }, [summary?.screen]);
71
78
 
72
79
  const fetchSummaryDetail = useCallback(async () => {
73
80
  const { success, data } = await axiosGet(
74
- API.UNIT.UNIT_SUMMARY_DETAIL(unit.id, summary.id),
81
+ API.UNIT.UNIT_SUMMARY_DETAIL(unit?.id, summary?.id),
75
82
  {},
76
83
  true
77
84
  );
@@ -80,7 +87,41 @@ const UnitSummary = memo(({ route }) => {
80
87
  setSummaryDetail(data.data);
81
88
  setLastUpdated(data.data.last_updated);
82
89
  }
83
- }, [summary.id, unit.id]);
90
+ }, [summary?.id, unit?.id]);
91
+
92
+ const fetchUnitDetail = useCallback(async () => {
93
+ const { success, data } = await axiosGet(
94
+ API.UNIT.UNIT_DETAIL(unit?.id),
95
+ {},
96
+ true
97
+ );
98
+ if (success) {
99
+ setUnit(data);
100
+ }
101
+ }, [unit.id]);
102
+
103
+ const fetchUnitSummary = useCallback(async () => {
104
+ const { success, data } = await axiosGet(
105
+ API.UNIT.UNIT_SUMMARY(unit?.id),
106
+ {}
107
+ );
108
+ if (success) {
109
+ const newData = data.filter((item) => item.id === summaryId);
110
+ setSummary(newData[0] || {});
111
+ }
112
+ }, [summaryId, unit?.id]);
113
+
114
+ useEffect(() => {
115
+ if (!unitData && unitId) {
116
+ fetchUnitDetail();
117
+ }
118
+ }, [fetchUnitDetail, unitData, unitId]);
119
+
120
+ useEffect(() => {
121
+ if (!summaryData && summaryId) {
122
+ fetchUnitSummary();
123
+ }
124
+ }, [fetchUnitSummary, summaryData, summaryId]);
84
125
 
85
126
  useEffect(() => {
86
127
  setLoading(true);
@@ -99,8 +140,8 @@ const UnitSummary = memo(({ route }) => {
99
140
  }, [fetchSummaryDetail]);
100
141
 
101
142
  const UnitSummaryDetail = getComponentAndGuide();
102
- const ComponentName = UnitSummaryDetail.componentName;
103
- const GuideName = UnitSummaryDetail.guideName;
143
+ const ComponentName = UnitSummaryDetail?.componentName;
144
+ const GuideName = UnitSummaryDetail?.guideName;
104
145
 
105
146
  const lastUpdatedStr = lastUpdated
106
147
  ? `${t('last_updated')} ${timeDifference(moment(), moment(lastUpdated))}`
@@ -109,7 +150,7 @@ const UnitSummary = memo(({ route }) => {
109
150
  return (
110
151
  <View style={[styles.container]}>
111
152
  <WrapHeaderScrollable
112
- title={summary.name}
153
+ title={summary?.name}
113
154
  subTitle={lastUpdatedStr}
114
155
  rightComponent={
115
156
  GuideName ? (
@@ -130,7 +171,9 @@ const UnitSummary = memo(({ route }) => {
130
171
  loading={loading}
131
172
  onRefresh={onRefresh}
132
173
  >
133
- <ComponentName summaryDetail={summaryDetail} unit={unit} />
174
+ {ComponentName && (
175
+ <ComponentName summaryDetail={summaryDetail} unit={unit} />
176
+ )}
134
177
  </WrapHeaderScrollable>
135
178
  </View>
136
179
  );
@@ -668,6 +668,13 @@
668
668
  "text_notification_content_pay_fine_successfully": "Your violation **%{booking}** has been paid successfully.",
669
669
  "text_notification_content_pay_fine_and_extend_successfully": "Your violation **%{booking_id_old}** has been paid successfully. The new parking session **%{booking_id_new}** is now extended until **%{leave_time}**.",
670
670
  "text_notification_content_stop_violation_free_parking_zone": "Free parking zone. You have an unpaid violation. Please complete your fine.",
671
+ "text_notification_content_air_quality_high": "The Air Quality index at **%{unit_name}** is **%{status}**. Wear a mask and reduce outdoor exercise.",
672
+ "text_notification_content_uv_index_high": "The UV index at **%{unit_name}** is **%{status}**. Reduce time in the sun and protect your skin, eye.",
673
+ "text_notification_content_pH_index_high": "The pH index at **%{unit_name}** is **%{status}**. Water is not safe to drink.",
674
+ "text_notification_content_clo_high": "The Chlorine residual at **%{unit_name}** is **%{status}**. Water is not safe to drink.",
675
+ "text_notification_content_turbility_high": "The Turbility at **%{unit_name}** is **%{status}**. Water is not safe to drink.",
676
+ "text_notification_content_smoke": "Smoke appears in **%{unit_name}**. Please check your home and call the rescue team if there is a fire.",
677
+ "text_notification_content_fire": "There is a fire at **%{unit_name}**, Please move out of the house immediately and call the rescue team.",
671
678
  "text_notification_content_remove_unit_to_owner": "Unit **%{unit_name}** has been removed successfully.",
672
679
  "text_notification_content_remove_unit_to_member": "Unit **%{unit_name}** has been removed by **%{unit_owner_name}**. You cannot access to this unit anymore.",
673
680
  "text_notification_content_remove_member": "You were remove from **%{unit_name}** by **%{unit_owner_name}**. You cannot access to this unit anymore.",
@@ -676,6 +676,13 @@
676
676
  "text_notification_content_pay_fine_successfully": "Đỗ xe vi phạm **%{booking}** của bạn đã được thanh toán.",
677
677
  "text_notification_content_pay_fine_and_extend_successfully": "Đỗ xe vi phạm **%{oldbooking}** của bạn đã được thanh toán thành công. Phiên đỗ xe mới **%{newbooking}** sẽ kết thúc vào lúc **%{time}**.",
678
678
  "text_notification_content_stop_violation_free_parking_zone": "Thời gian đậu xe miễn phí. Bạn có một vi phạm chưa được thanh toán. Vui lòng hoàn thành tiền phạt của bạn.",
679
+ "text_notification_content_air_quality_high": "Chỉ số AQI tại **%{unit_name}** đang ở mức **%{status}**. Đeo khẩu trang và giảm các hoạt động ngoài trời để bảo vệ sức khoẻ của bạn.",
680
+ "text_notification_content_uv_index_high": "Chỉ số UV tại **%{unit_name}** đang ở mức **%{status}**. Giảm thời gian ngoài trời và bảo vệ da, mắt khỏi ánh nắng mặt trời.",
681
+ "text_notification_content_pH_index_high": "Độ pH của nước tại **%{unit_name}** đang ở mức **%{status}**. Nước không đảm bảo an toàn để uống.",
682
+ "text_notification_content_clo_high": "Độ clo của nước tại **%{unit_name}** đang ở mức **%{status}**. Nước không đảm bảo an toàn để uống.",
683
+ "text_notification_content_turbility_high": "Độ đục của nước tại **%{unit_name}** đang ở mức **%{status}**. Nước không đảm bảo an toàn để uống.",
684
+ "text_notification_content_smoke": "Xuất hiện khói tại **%{unit_name}**. Vui lòng kiểm tra nhà của bạn và gọi cho đội cứu hộ nếu xảy ra hỏa hoạn.",
685
+ "text_notification_content_fire": "Có đám cháy tại **%{unit_name}**, Vui lòng di chuyển ra khỏi nhà ngay lập tức và gọi cho đội cứu hộ.",
679
686
  "text_notification_content_remove_unit_to_owner": "Địa điểm **%{unit_name}** vừa được xoá thành công.",
680
687
  "text_notification_content_remove_unit_to_member": "Địa điểm **%{unit_name}** vừa được xoá bởi **%{unit_owner_name}**. Bạn không thể truy cập vào địa điểm này được nữa.",
681
688
  "text_notification_content_remove_member": "Bạn vừa được xoá khỏi **%{unit_name}** bởi **%{unit_owner_name}**. Bạn không thể truy cập vào địa điểm này được nữa.",