@eohjsc/react-native-smart-city 0.2.59 → 0.2.63

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 (101) hide show
  1. package/README.md +115 -68
  2. package/assets/images/Map/MarkerGeolocation.svg +4 -0
  3. package/package.json +3 -3
  4. package/src/commons/ActionGroup/CurtainButtonTemplate.js +10 -2
  5. package/src/commons/ActionGroup/__test__/CurtainButtonTemplate.test.js +1 -1
  6. package/src/commons/ActionGroup/__test__/MenuActionAddSchedule.test.js +71 -0
  7. package/src/commons/ActionGroup/hooks/AccessScheduleDetailStyles.js +41 -0
  8. package/src/commons/ActionGroup/hooks/MenuActionAddSchedule.js +110 -0
  9. package/src/commons/ActionGroup/hooks/MenuActionAddScheduleStyle.js +69 -0
  10. package/src/commons/ActionGroup/hooks/RecurringDetail.js +97 -0
  11. package/src/commons/DateTimeRangeChange/DateTimeButton.js +7 -2
  12. package/src/commons/Device/HistoryChart.js +80 -81
  13. package/src/commons/Device/HorizontalBarChart.js +48 -31
  14. package/src/commons/Device/LinearChart.js +28 -1
  15. package/src/commons/Form/CurrencyInput.js +1 -0
  16. package/src/commons/FourButtonFilterHistory/__test__/FourButtonFilterHistory.test.js +48 -0
  17. package/src/commons/FourButtonFilterHistory/index.js +72 -0
  18. package/src/commons/FourButtonFilterHistory/styles.js +22 -0
  19. package/src/commons/ImagePicker/index.js +27 -33
  20. package/src/commons/MediaPlayerDetail/Styles/MediaPlayerDetailStyles.js +11 -1
  21. package/src/commons/MediaPlayerDetail/index.js +14 -5
  22. package/src/commons/SubUnit/OneTap/OneTapStyles.js +20 -1
  23. package/src/commons/SubUnit/OneTap/__test__/SubUnitAutomate.test.js +151 -40
  24. package/src/commons/SubUnit/OneTap/index.js +64 -12
  25. package/src/commons/UnitSummary/AirQuality/index.js +9 -7
  26. package/src/commons/UnitSummary/ConfigHistoryChart.js +2 -1
  27. package/src/configs/API.js +3 -0
  28. package/src/configs/Constants.js +15 -0
  29. package/src/iot/RemoteControl/Bluetooth.js +6 -3
  30. package/src/iot/RemoteControl/GoogleHome.js +6 -3
  31. package/src/iot/RemoteControl/Internet.js +1 -0
  32. package/src/iot/RemoteControl/LG.js +2 -1
  33. package/src/iot/RemoteControl/index.js +13 -6
  34. package/src/navigations/SharedStack.js +11 -9
  35. package/src/navigations/UnitStack.js +26 -2
  36. package/src/screens/ActivityLog/ItemLog.js +3 -3
  37. package/src/screens/ActivityLog/__test__/ItemLog.test.js +5 -2
  38. package/src/screens/ActivityLog/hooks/index.js +2 -1
  39. package/src/screens/ActivityLog/index.js +0 -1
  40. package/src/screens/AddLocationMaps/index.js +4 -2
  41. package/src/screens/AddNewAction/SelectSensorDevices.js +18 -11
  42. package/src/screens/AddNewAction/Styles/SelectSensorDevicesStyles.js +5 -1
  43. package/src/screens/AddNewAction/__test__/SelectSensorDevices.test.js +6 -1
  44. package/src/screens/Automate/MultiUnits.js +7 -4
  45. package/src/screens/Automate/__test__/MultiUnits.test.js +1 -1
  46. package/src/screens/Automate/__test__/index.test.js +12 -0
  47. package/src/screens/ConfirmUnitDeletion/__test__/ConfirmUnitDeletion.test.js +61 -0
  48. package/src/screens/ConfirmUnitDeletion/index.js +64 -0
  49. package/src/screens/ConfirmUnitDeletion/styles.js +37 -0
  50. package/src/screens/Device/__test__/detail.test.js +3 -2
  51. package/src/screens/Device/detail.js +48 -15
  52. package/src/screens/Device/hooks/useDisconnectedDevice.js +2 -1
  53. package/src/screens/Device/styles.js +3 -3
  54. package/src/screens/EmergencySetting/__test__/DropDownItem.test.js +59 -0
  55. package/src/screens/EmergencySetting/__test__/index.test.js +27 -0
  56. package/src/screens/EmergencySetting/components/DropDownItem.js +54 -0
  57. package/src/screens/EmergencySetting/index.js +92 -0
  58. package/src/screens/EmergencySetting/styles/DropDownItem.js +38 -0
  59. package/src/screens/EmergencySetting/styles.js +25 -0
  60. package/src/screens/MoveToAnotherSubUnit/__test__/index.test.js +126 -0
  61. package/src/screens/MoveToAnotherSubUnit/index.js +88 -0
  62. package/src/screens/MoveToAnotherSubUnit/styles/MoveToAnotherSubUnitStyles.js +50 -0
  63. package/src/screens/ScriptDetail/Styles/indexStyles.js +0 -1
  64. package/src/screens/ScriptDetail/index.js +1 -0
  65. package/src/screens/SubUnit/AddSubUnit.js +3 -3
  66. package/src/screens/SubUnit/AddSubUnitStyles.js +0 -2
  67. package/src/screens/SubUnit/EditSubUnit.js +16 -7
  68. package/src/screens/SubUnit/EditSubUnitStyles.js +2 -3
  69. package/src/screens/SubUnit/__test__/EditSubUnit.test.js +2 -2
  70. package/src/screens/TDSGuide/index.js +1 -1
  71. package/src/screens/Unit/ChooseLocation.js +3 -7
  72. package/src/screens/Unit/ChooseLocationStyles.js +5 -8
  73. package/src/screens/Unit/Detail.js +16 -6
  74. package/src/screens/Unit/ManageUnit.js +20 -26
  75. package/src/screens/Unit/SmartAccount.js +25 -41
  76. package/src/screens/Unit/SmartAccountItem.js +2 -1
  77. package/src/screens/Unit/SmartAccountStyles.js +0 -1
  78. package/src/screens/Unit/__test__/ManageUnit.test.js +0 -6
  79. package/src/screens/Unit/__test__/SmartAccount.test.js +24 -0
  80. package/src/screens/Unit/__test__/SmartAccountItem.test.js +72 -0
  81. package/src/screens/UnitSummary/components/3PPowerConsumption/index.js +58 -59
  82. package/src/screens/UnitSummary/components/PowerConsumption/index.js +26 -22
  83. package/src/screens/UnitSummary/components/Temperature/ItemTemperature/index.js +2 -2
  84. package/src/screens/UnitSummary/components/Temperature/index.js +15 -14
  85. package/src/screens/UnitSummary/components/UvIndex/index.js +6 -5
  86. package/src/screens/UnitSummary/components/WaterQuality/index.js +9 -7
  87. package/src/screens/UnitSummary/index.js +11 -7
  88. package/src/screens/WaterQualityGuide/index.js +1 -0
  89. package/src/utils/Apis/axios.js +4 -4
  90. package/src/utils/I18n/translations/en.json +20 -2
  91. package/src/utils/I18n/translations/vi.json +21 -2
  92. package/src/utils/Route/index.js +3 -0
  93. package/src/utils/Utils.js +4 -0
  94. package/src/commons/ThreeButtonHistory/CalendarHeader.js +0 -35
  95. package/src/commons/ThreeButtonHistory/CalendarHeaderStyles.js +0 -17
  96. package/src/commons/ThreeButtonHistory/SelectMonth.js +0 -53
  97. package/src/commons/ThreeButtonHistory/SelectMonthStyles.js +0 -29
  98. package/src/commons/ThreeButtonHistory/__test__/SelectMonth.test.js +0 -37
  99. package/src/commons/ThreeButtonHistory/__test__/ThreeButtonHistory.test.js +0 -240
  100. package/src/commons/ThreeButtonHistory/index.js +0 -310
  101. package/src/commons/ThreeButtonHistory/styles.js +0 -65
@@ -59,8 +59,11 @@ const DeviceDetail = ({ route }) => {
59
59
  internet: {},
60
60
  });
61
61
  // eslint-disable-next-line no-unused-vars
62
- const [loading, setLoading] = useState(true);
63
- const [isConnected, setConnected] = useState(true);
62
+ const [loading, setLoading] = useState({
63
+ isConnected: true,
64
+ displayTemplate: true,
65
+ });
66
+ const [isConnected, setConnected] = useState(false);
64
67
  const [lastUpdated, setLastUpdated] = useState(null);
65
68
  const [lastEvent, setLastEvent] = useState({ id: 0, reportedAt: 0 });
66
69
  const [maxValue, setMaxValue] = useState(60);
@@ -162,7 +165,7 @@ const DeviceDetail = ({ route }) => {
162
165
  }
163
166
  }
164
167
  }
165
- setLoading(false);
168
+ setLoading((preState) => ({ ...preState, displayTemplate: false }));
166
169
 
167
170
  const controlResult = await axiosGet(
168
171
  API.SENSOR.REMOTE_CONTROL_OPTIONS(sensor.id),
@@ -211,9 +214,10 @@ const DeviceDetail = ({ route }) => {
211
214
  data: {
212
215
  id: sensor.id,
213
216
  type: 'action',
217
+ share: unit,
214
218
  filterEnabled: {
215
- date: false,
216
- user: false,
219
+ date: true,
220
+ user: Boolean(unit.id),
217
221
  },
218
222
  },
219
223
  text: t('activity_log'),
@@ -234,6 +238,11 @@ const DeviceDetail = ({ route }) => {
234
238
  data: { unit, sensor },
235
239
  text: t('manage_access'),
236
240
  });
241
+ menuItems.push({
242
+ text: t('move_to_another_sub_unit'),
243
+ route: Routes.MoveToAnotherSubUnit,
244
+ data: { unit, sensor, station },
245
+ });
237
246
  }
238
247
  if (isShowSetupEmergencyContact) {
239
248
  menuItems.push({
@@ -270,15 +279,15 @@ const DeviceDetail = ({ route }) => {
270
279
  return [...menuItems];
271
280
  }, [
272
281
  display.items,
282
+ isOwner,
283
+ isShowSetupEmergencyContact,
273
284
  t,
274
285
  isFavourite,
275
- emergencyDeviceId,
276
- isShowSetupEmergencyContact,
277
286
  sensor,
278
- setSensorName,
279
287
  sensorName,
280
- isOwner,
281
288
  unit,
289
+ station,
290
+ emergencyDeviceId,
282
291
  addToFavorites,
283
292
  removeFromFavorites,
284
293
  ]);
@@ -354,11 +363,14 @@ const DeviceDetail = ({ route }) => {
354
363
  transformDatetime(data, ['last_updated']);
355
364
  setLastUpdated(data.last_updated);
356
365
  }
366
+ setLoading((preState) => ({ ...preState, isConnected: false }));
357
367
  };
358
368
  if (sensor.is_managed_by_backend && !sensor.is_other_device) {
359
369
  const updateInterval = setInterval(() => fetchValues(), 5000);
360
370
  fetchValues();
361
371
  return () => clearInterval(updateInterval);
372
+ } else {
373
+ setLoading((preState) => ({ ...preState, isConnected: false }));
362
374
  }
363
375
  }, [sensor, display]);
364
376
 
@@ -444,7 +456,7 @@ const DeviceDetail = ({ route }) => {
444
456
  `@CACHE_REQUEST_${API.SENSOR.REMOTE_CONTROL_OPTIONS(sensor.id)}`
445
457
  );
446
458
  controlOptionData && setControlOptions(JSON.parse(controlOptionData));
447
- setLoading(false);
459
+ setLoading((preState) => ({ ...preState, displayTemplate: false }));
448
460
  };
449
461
 
450
462
  const onItemMenuClicked = (item) => {
@@ -469,6 +481,10 @@ const DeviceDetail = ({ route }) => {
469
481
  showPopoverWithRef(refMenuAction);
470
482
  }, [showPopoverWithRef, refMenuAction]);
471
483
 
484
+ const onPressSetting = useCallback(() => {
485
+ navigation.navigate(Routes.EmergencySetting);
486
+ }, [navigation]);
487
+
472
488
  const HeaderRight = useMemo(
473
489
  () => (
474
490
  <View style={styles.headerRight}>
@@ -483,6 +499,13 @@ const DeviceDetail = ({ route }) => {
483
499
  <IconOutline name="star" size={25} />
484
500
  )}
485
501
  </TouchableOpacity>
502
+
503
+ {isShowSetupEmergencyContact && (
504
+ <TouchableOpacity style={styles.button} onPress={onPressSetting}>
505
+ <Icon name="setting" size={25} color={Colors.Black} />
506
+ </TouchableOpacity>
507
+ )}
508
+
486
509
  <TouchableOpacity
487
510
  style={styles.button}
488
511
  onPress={handleShowMenuAction}
@@ -493,7 +516,14 @@ const DeviceDetail = ({ route }) => {
493
516
  </TouchableOpacity>
494
517
  </View>
495
518
  ),
496
- [isFavourite, addToFavorites, removeFromFavorites, handleShowMenuAction]
519
+ [
520
+ isFavourite,
521
+ removeFromFavorites,
522
+ addToFavorites,
523
+ isShowSetupEmergencyContact,
524
+ onPressSetting,
525
+ handleShowMenuAction,
526
+ ]
497
527
  );
498
528
 
499
529
  return (
@@ -505,15 +535,18 @@ const DeviceDetail = ({ route }) => {
505
535
  onRefresh={onRefresh}
506
536
  >
507
537
  <View style={styles.wrapTemplate}>
508
- {!loading && renderSensorConnected()}
538
+ {loading.displayTemplate === false &&
539
+ loading.isConnected === false &&
540
+ netInfo.isConnected !== null &&
541
+ renderSensorConnected()}
509
542
  </View>
510
543
  {isShowSetupEmergencyContact && canManageSubUnit && (
511
544
  <BottomButtonView
512
545
  style={styles.bottomButtonEmergencyContact}
513
- mainIcon={<Icon name="phone" size={24} color={Colors.Gray9} />}
514
- mainTitle={t('emergency_contacts')}
546
+ mainIcon={<Icon name="plus" size={16} color={Colors.Primary} />}
547
+ mainTitle={t('setup_my_emergency_contact')}
515
548
  onPressMain={onSetupContacts}
516
- typeMain="CardShadow"
549
+ typeMain="primaryBorder"
517
550
  semiboldMain={false}
518
551
  />
519
552
  )}
@@ -39,7 +39,8 @@ export const useDisconnectedDevice = (sensorName, isDeviceHasBle) => {
39
39
 
40
40
  const checkNetWorkConnect = useCallback(
41
41
  async (isHavingInternet, isBtEnabled) => {
42
- if (!isHavingInternet && isDeviceHasBle) {
42
+ if (isHavingInternet === false && isDeviceHasBle) {
43
+ // TODO avoid case first render isHavingInternet == null
43
44
  if (isBtEnabled === true) {
44
45
  ToastBottomHelper.info(
45
46
  t('your_internet_is_disconnected', { name: sensorName }),
@@ -32,7 +32,7 @@ export default StyleSheet.create({
32
32
  },
33
33
  bottomButtonEmergencyContact: {
34
34
  marginHorizontal: 16,
35
- marginBottom: 32,
35
+ marginBottom: 40,
36
36
  },
37
37
  locationName: {
38
38
  flex: 1,
@@ -77,10 +77,10 @@ export default StyleSheet.create({
77
77
  buttonStar: {
78
78
  justifyContent: 'center',
79
79
  alignItems: 'center',
80
+ marginRight: 14,
80
81
  },
81
82
  button: {
82
- width: 35,
83
- height: 40,
83
+ marginRight: 10,
84
84
  justifyContent: 'center',
85
85
  alignItems: 'center',
86
86
  },
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+ import { create, act } from 'react-test-renderer';
3
+ import { TouchableOpacity } from 'react-native';
4
+
5
+ import { SCProvider } from '../../../context';
6
+ import { mockSCStore } from '../../../context/mockStore';
7
+ import DropDownItem from '../components/DropDownItem';
8
+ import Text from '../../../commons/Text';
9
+ import { TESTID } from '../../../configs/Constants';
10
+
11
+ const mockonOpen = jest.fn();
12
+
13
+ const wrapComponent = (props) => (
14
+ <SCProvider initState={mockSCStore({})}>
15
+ <DropDownItem {...props} />
16
+ </SCProvider>
17
+ );
18
+
19
+ describe('Test DropDownItem', () => {
20
+ let tree;
21
+
22
+ test('test render DropDownItem', () => {
23
+ const props = {
24
+ label: 'mode',
25
+ data: [{ label: 'Stop (0)', value: 'stop' }],
26
+ isOpen: true,
27
+ };
28
+
29
+ act(() => {
30
+ tree = create(wrapComponent(props));
31
+ });
32
+ const instance = tree.root;
33
+ const dropDownPicker = instance.findAll(
34
+ (el) =>
35
+ el.props.testID === TESTID.DROP_DOWN_PICKER_ITEM && el.type === Text
36
+ );
37
+ expect(dropDownPicker).toHaveLength(1);
38
+ });
39
+
40
+ test('test onPress DropDown', () => {
41
+ const props = {
42
+ label: 'mode',
43
+ data: [{ label: 'Stop (0)', value: 'stop' }],
44
+ onOpen: mockonOpen,
45
+ index: 0,
46
+ };
47
+
48
+ act(() => {
49
+ tree = create(wrapComponent(props));
50
+ });
51
+ const instance = tree.root;
52
+ const touchableOpacity = instance.findByType(TouchableOpacity);
53
+ act(() => {
54
+ touchableOpacity.props.onPress();
55
+ });
56
+
57
+ expect(mockonOpen).toHaveBeenCalledWith(props.index);
58
+ });
59
+ });
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { create, act } from 'react-test-renderer';
3
+
4
+ import { SCProvider } from '../../../context';
5
+ import { mockSCStore } from '../../../context/mockStore';
6
+ import EmergencySetting from '..';
7
+ import DropDownItem from '../components/DropDownItem';
8
+
9
+ const wrapComponent = () => (
10
+ <SCProvider initState={mockSCStore({})}>
11
+ <EmergencySetting />
12
+ </SCProvider>
13
+ );
14
+
15
+ describe('test EmergencySetting', () => {
16
+ let tree;
17
+
18
+ test('test render EmergencySetting', () => {
19
+ act(() => {
20
+ tree = create(wrapComponent());
21
+ });
22
+ const instance = tree.root;
23
+ const dropDownItem = instance.findAllByType(DropDownItem);
24
+
25
+ expect(dropDownItem.length).toEqual(3);
26
+ });
27
+ });
@@ -0,0 +1,54 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import { View, TouchableOpacity } from 'react-native';
3
+ import { IconFill } from '@ant-design/icons-react-native';
4
+
5
+ import Text from '../../../commons/Text';
6
+ import styles from '../styles/DropDownItem';
7
+ import { Colors } from '../../../configs';
8
+ import { TESTID } from '../../../configs/Constants';
9
+
10
+ const DropDownItem = ({ label, data, onSelectItem, isOpen, onOpen, index }) => {
11
+ const [selecteValue, setSelectValue] = useState(data[0] || {});
12
+
13
+ const handleSelectItem = useCallback(
14
+ (item) => () => {
15
+ setSelectValue(item);
16
+ onSelectItem && onSelectItem();
17
+ },
18
+ [onSelectItem]
19
+ );
20
+
21
+ const handleOnOpen = useCallback(() => {
22
+ onOpen && onOpen(index);
23
+ }, [index, onOpen]);
24
+
25
+ return (
26
+ // eslint-disable-next-line react-native/no-inline-styles
27
+ <View style={[styles.wrap, { zIndex: isOpen ? 1 : 0 }]}>
28
+ {label && <Text>{label}</Text>}
29
+ <View style={styles.dropDownContainer}>
30
+ <TouchableOpacity style={styles.dropDownStyle} onPress={handleOnOpen}>
31
+ <Text>{selecteValue?.label}</Text>
32
+ <IconFill name="caret-down" size={18} color={Colors.Gray8} />
33
+ </TouchableOpacity>
34
+
35
+ {isOpen && (
36
+ <View style={[styles.dropDownItem]}>
37
+ {data.map((item, index) => (
38
+ <Text
39
+ style={styles.dropDownText}
40
+ onPress={handleSelectItem(item)}
41
+ key={index}
42
+ testID={TESTID.DROP_DOWN_PICKER_ITEM}
43
+ >
44
+ {item?.label}
45
+ </Text>
46
+ ))}
47
+ </View>
48
+ )}
49
+ </View>
50
+ </View>
51
+ );
52
+ };
53
+
54
+ export default DropDownItem;
@@ -0,0 +1,92 @@
1
+ import React, { useState, useCallback, useMemo } from 'react';
2
+ import { View, ScrollView } from 'react-native';
3
+
4
+ import { useTranslations } from '../../hooks/Common/useTranslations';
5
+
6
+ import { HeaderCustom } from '../../commons/Header';
7
+ import DropDownItem from './components/DropDownItem';
8
+ import _TextInput from '../../commons/Form/TextInput';
9
+
10
+ import styles from './styles';
11
+ import Text from '../../commons/Text';
12
+
13
+ const EmergencySetting = () => {
14
+ const t = useTranslations();
15
+ const [openedDropdown, setOpenedDropdown] = useState(null);
16
+ const [duration, setDuration] = useState('');
17
+
18
+ const onChangeDuration = useCallback((text) => {
19
+ setDuration(text);
20
+ }, []);
21
+
22
+ const listData = useMemo(() => {
23
+ return [
24
+ {
25
+ label: t('mode'),
26
+ data: [
27
+ { label: 'Stop (0)', value: 'stop' },
28
+ { label: 'Stop (1)', value: 'stop' },
29
+ { label: 'Stop (2)', value: 'stop' },
30
+ ],
31
+ },
32
+ {
33
+ label: t('level'),
34
+ data: [
35
+ { label: 'Low', value: 'low' },
36
+ { label: 'High', value: 'High' },
37
+ ],
38
+ },
39
+ {
40
+ label: t('strobe'),
41
+ data: [
42
+ { label: 'True', value: 'true' },
43
+ { label: 'False', value: 'true' },
44
+ ],
45
+ },
46
+ ];
47
+ }, [t]);
48
+
49
+ const handleOnOpen = useCallback((index) => {
50
+ setOpenedDropdown((oldIndex) => {
51
+ if (index === oldIndex) {
52
+ return null;
53
+ }
54
+ return index;
55
+ });
56
+ }, []);
57
+
58
+ const handleOnSelectItem = useCallback(() => {
59
+ setOpenedDropdown(null);
60
+ }, []);
61
+
62
+ return (
63
+ <View style={styles.wrap}>
64
+ <HeaderCustom title={t('setting')} isShowSeparator />
65
+
66
+ <ScrollView contentContainerStyle={styles.contentContainerStyle}>
67
+ {listData.map((item, index) => (
68
+ <DropDownItem
69
+ {...item}
70
+ key={index}
71
+ index={index}
72
+ onOpen={handleOnOpen}
73
+ isOpen={openedDropdown === index}
74
+ onSelectItem={handleOnSelectItem}
75
+ />
76
+ ))}
77
+
78
+ <Text style={styles.duration}>{t('duration')}</Text>
79
+ <_TextInput
80
+ wrapStyle={styles.wrapInput}
81
+ textInputStyle={styles.textInputStyle}
82
+ keyboardType="numeric"
83
+ onChange={onChangeDuration}
84
+ value={duration}
85
+ placeholder="0"
86
+ />
87
+ </ScrollView>
88
+ </View>
89
+ );
90
+ };
91
+
92
+ export default EmergencySetting;
@@ -0,0 +1,38 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { Colors } from '../../../configs';
3
+
4
+ export default StyleSheet.create({
5
+ wrap: {
6
+ paddingHorizontal: 16,
7
+ paddingTop: 16,
8
+ },
9
+ dropDownContainer: {
10
+ marginTop: 8,
11
+ },
12
+ dropDownStyle: {
13
+ flexDirection: 'row',
14
+ justifyContent: 'space-between',
15
+ alignItems: 'center',
16
+ borderColor: Colors.Gray5,
17
+ borderRadius: 2,
18
+ borderWidth: 1,
19
+ paddingHorizontal: 16,
20
+ paddingVertical: 8,
21
+ },
22
+ dropDownItem: {
23
+ borderColor: Colors.Gray5,
24
+ borderRadius: 2,
25
+ borderTopWidth: 0,
26
+ borderWidth: 1,
27
+ paddingHorizontal: 16,
28
+ paddingBottom: 8,
29
+ backgroundColor: Colors.White,
30
+ position: 'absolute',
31
+ width: '100%',
32
+ top: 44,
33
+ left: 0,
34
+ },
35
+ dropDownText: {
36
+ marginTop: 8,
37
+ },
38
+ });
@@ -0,0 +1,25 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { Colors } from '../../configs';
3
+
4
+ export default StyleSheet.create({
5
+ wrap: {
6
+ flex: 1,
7
+ backgroundColor: Colors.White,
8
+ },
9
+ contentContainerStyle: {
10
+ flexGrow: 1,
11
+ },
12
+ wrapInput: {
13
+ paddingHorizontal: 16,
14
+ marginTop: 0,
15
+ },
16
+ textInputStyle: {
17
+ paddingTop: 7,
18
+ paddingBottom: 7,
19
+ },
20
+ duration: {
21
+ marginTop: 14,
22
+ marginBottom: 10,
23
+ marginLeft: 16,
24
+ },
25
+ });
@@ -0,0 +1,126 @@
1
+ import React from 'react';
2
+ import axios from 'axios';
3
+ import { create } from 'react-test-renderer';
4
+ import { act } from '@testing-library/react-hooks';
5
+
6
+ import { TESTID } from '../../../configs/Constants';
7
+ import { API } from '../../../configs';
8
+ import MoveToAnotherSubUnit from '../';
9
+ import { SCProvider } from '../../../context';
10
+ import { mockSCStore } from '../../../context/mockStore';
11
+ import BottomButtonView from '../../../commons/BottomButtonView';
12
+
13
+ jest.mock('axios');
14
+ jest.mock('react', () => {
15
+ return {
16
+ ...jest.requireActual('react'),
17
+ memo: (x) => x,
18
+ };
19
+ });
20
+
21
+ const wrapComponent = (route) => (
22
+ <SCProvider initState={mockSCStore({})}>
23
+ <MoveToAnotherSubUnit route={route} />
24
+ </SCProvider>
25
+ );
26
+
27
+ describe('Test Render ListSubUnit', () => {
28
+ let tree;
29
+ let route = {
30
+ params: {
31
+ unit: {
32
+ id: 200,
33
+ stations: [
34
+ {
35
+ id: 1,
36
+ name: 'Favourite',
37
+ },
38
+ {
39
+ id: 2,
40
+ name: 'Sceriano',
41
+ },
42
+ {
43
+ id: 3,
44
+ name: 'Room A',
45
+ sensors: [
46
+ {
47
+ action: null,
48
+ action2: null,
49
+ chip_id: 40,
50
+ description: null,
51
+ icon: '',
52
+ icon_kit: '',
53
+ id: 73,
54
+ is_managed_by_backend: true,
55
+ is_other_device: false,
56
+ name: 'Multi-Air Quality',
57
+ quick_action: null,
58
+ },
59
+ ],
60
+ },
61
+ {
62
+ id: 4,
63
+ name: 'Room 2',
64
+ sensors: [],
65
+ },
66
+ ],
67
+ },
68
+ station: {
69
+ id: 3,
70
+ name: 'Room 1',
71
+ },
72
+ sensor: {
73
+ action: null,
74
+ action2: null,
75
+ chip_id: 40,
76
+ description: null,
77
+ icon: '',
78
+ icon_kit: '',
79
+ id: 73,
80
+ is_managed_by_backend: true,
81
+ is_other_device: false,
82
+ name: 'Multi-Air Quality',
83
+ quick_action: null,
84
+ },
85
+ },
86
+ };
87
+
88
+ it('render ListSubUnit', async () => {
89
+ await act(async () => {
90
+ tree = await create(wrapComponent(route));
91
+ });
92
+ const instance = tree.root;
93
+ const rowSubUnit = instance.findAll(
94
+ (el) => el.props.testID === TESTID.ROW_SUB_UNIT
95
+ );
96
+ expect(rowSubUnit).toHaveLength(2);
97
+ });
98
+
99
+ it('test move sensor', async () => {
100
+ await act(async () => {
101
+ tree = await create(wrapComponent(route));
102
+ });
103
+ const instance = tree.root;
104
+ const bottomButtonView = instance.findByType(BottomButtonView);
105
+ const rowSubUnit = instance.findAll(
106
+ (el) => el.props.testID === TESTID.ROW_SUB_UNIT
107
+ );
108
+ act(() => {
109
+ rowSubUnit[1].props.onSelect({
110
+ id: 4,
111
+ name: 'Room 2',
112
+ sensors: [],
113
+ });
114
+ });
115
+ act(() => {
116
+ bottomButtonView.props.onPressMain();
117
+ });
118
+
119
+ expect(axios.patch).toHaveBeenCalledWith(
120
+ API.SENSOR.CHANGE_SUB_UNIT(200, 3, 73),
121
+ {
122
+ station_id: 4,
123
+ }
124
+ );
125
+ });
126
+ });
@@ -0,0 +1,88 @@
1
+ import React, { useState, useCallback, memo, useMemo } from 'react';
2
+ import { View, ScrollView, TouchableOpacity } from 'react-native';
3
+ import { useNavigation } from '@react-navigation/native';
4
+
5
+ import { useTranslations } from '../../hooks/Common/useTranslations';
6
+ import { HeaderCustom } from '../../commons/Header';
7
+ import styles from './styles/MoveToAnotherSubUnitStyles';
8
+ import { Colors } from '../../configs';
9
+ import Text from '../../commons/Text';
10
+ import RadioCircle from '../../commons/RadioCircle';
11
+ import BottomButtonView from '../../commons/BottomButtonView';
12
+ import Routes from '../../utils/Route';
13
+ import { axiosPatch } from '../../utils/Apis/axios';
14
+ import { API } from '../../configs';
15
+ import { TESTID } from '../../configs/Constants';
16
+
17
+ const RowSubUnit = ({ subUnit, isSelected, onSelect }) => {
18
+ const handleOnPress = useCallback(() => {
19
+ onSelect(subUnit);
20
+ }, [onSelect, subUnit]);
21
+ return (
22
+ <TouchableOpacity style={styles.rowSubUnit} onPress={handleOnPress}>
23
+ <RadioCircle active={isSelected} />
24
+ <View style={styles.wrapText}>
25
+ <Text type="H4" color={Colors.Gray9}>
26
+ {subUnit.name}
27
+ </Text>
28
+ </View>
29
+ </TouchableOpacity>
30
+ );
31
+ };
32
+
33
+ const MoveToAnotherSubUnit = memo(({ route }) => {
34
+ const t = useTranslations();
35
+ const { params = {} } = route;
36
+ const { unit, sensor, station } = params;
37
+ const { navigate } = useNavigation();
38
+ const [selectedSubUnit, setSelectedSubUnit] = useState(station);
39
+
40
+ const listStationUnit = useMemo(() => {
41
+ return unit.stations.slice(2);
42
+ }, [unit.stations]);
43
+
44
+ const handleOnSelect = useCallback((item) => {
45
+ setSelectedSubUnit(item);
46
+ }, []);
47
+
48
+ const onSubmit = useCallback(async () => {
49
+ const { success } = await axiosPatch(
50
+ API.SENSOR.CHANGE_SUB_UNIT(unit.id, station.id, sensor.id),
51
+ {
52
+ station_id: selectedSubUnit.id,
53
+ }
54
+ );
55
+ if (success) {
56
+ navigate(Routes.UnitDetail);
57
+ }
58
+ }, [navigate, selectedSubUnit.id, sensor.id, station.id, unit.id]);
59
+
60
+ return (
61
+ <View style={styles.wrap}>
62
+ <HeaderCustom title={t('move_to_another_sub_unit')} isShowSeparator />
63
+ <ScrollView>
64
+ <View style={styles.container}>
65
+ <View>
66
+ {listStationUnit.map((item, index) => (
67
+ <RowSubUnit
68
+ subUnit={item}
69
+ isSelected={selectedSubUnit === item}
70
+ onSelect={handleOnSelect}
71
+ testID={TESTID.ROW_SUB_UNIT}
72
+ key={index}
73
+ />
74
+ ))}
75
+ </View>
76
+ </View>
77
+ </ScrollView>
78
+ <BottomButtonView
79
+ style={styles.bottomButtonView}
80
+ mainTitle={t('text_submit')}
81
+ onPressMain={onSubmit}
82
+ typeMain={'primary'}
83
+ />
84
+ </View>
85
+ );
86
+ });
87
+
88
+ export default MoveToAnotherSubUnit;