@eohjsc/react-native-smart-city 0.2.96 → 0.2.99

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 (109) hide show
  1. package/README.md +35 -14
  2. package/package.json +16 -4
  3. package/src/commons/Action/ItemQuickAction.js +5 -2
  4. package/src/commons/ActionGroup/ColorPickerTemplate.js +1 -1
  5. package/src/commons/ActionGroup/NumberUpDownActionTemplate.js +12 -4
  6. package/src/commons/ActionGroup/OnOffSmartLock/OnOffSmartLock.js +7 -4
  7. package/src/commons/ActionGroup/OnOffSmartLock/SetupGeneratePasscode/index.js +1 -0
  8. package/src/commons/ActionGroup/OnOffTemplate/OnOffSimpleTemplate.js +10 -10
  9. package/src/commons/ActionGroup/OnOffTemplate/index.js +18 -15
  10. package/src/commons/ActionGroup/OptionsDropdownActionTemplate.js +8 -2
  11. package/src/commons/ActionGroup/SliderRangeTemplate.js +1 -1
  12. package/src/commons/ActionGroup/SmartTiviActionTemplate/SmartTiviActionTemplate.js +4 -1
  13. package/src/commons/ActionGroup/StatesGridActionTemplate.js +14 -4
  14. package/src/commons/ActionGroup/TimerActionTemplate.js +9 -1
  15. package/src/commons/ActionGroup/TwoButtonTemplate/index.js +13 -9
  16. package/src/commons/ActionGroup/__test__/OnOffButtonTemplate.test.js +14 -14
  17. package/src/commons/ActionGroup/__test__/OnOffTemplate.test.js +53 -78
  18. package/src/commons/ActionGroup/__test__/OneBigButtonTemplate.test.js +36 -20
  19. package/src/commons/Auth/AccountList.js +1 -1
  20. package/src/commons/{Connecting → Connecting}/__test__/Connecting.test.js +0 -0
  21. package/src/commons/{Connecting → Connecting}/index.js +0 -0
  22. package/src/commons/{Connecting → Connecting}/styles.js +0 -0
  23. package/src/commons/ConnectingProcess/index.js +1 -1
  24. package/src/commons/Device/HistoryChart.js +6 -2
  25. package/src/commons/Device/PMSensor/PMSensorIndicatior.js +16 -12
  26. package/src/commons/Device/PMSensor/PMSensorIndicatorStyles.js +3 -0
  27. package/src/commons/Device/WaterQualitySensor/ListQualityIndicator.js +1 -0
  28. package/src/commons/FieldTemplate/ChooseUserField/ChooseFieldStyles.js +25 -0
  29. package/src/commons/FieldTemplate/ChooseUserField/ChoosePopup.js +96 -0
  30. package/src/commons/FieldTemplate/ChooseUserField/ChoosePopupStyles.js +39 -0
  31. package/src/commons/FieldTemplate/ChooseUserField/__test__/index.test.js +113 -0
  32. package/src/commons/FieldTemplate/ChooseUserField/index.js +62 -0
  33. package/src/commons/FieldTemplate/PasscodeField/PasscodeFieldStyles.js +30 -0
  34. package/src/commons/FieldTemplate/PasscodeField/__test__/index.test.js +93 -0
  35. package/src/commons/FieldTemplate/PasscodeField/index.js +43 -0
  36. package/src/commons/FieldTemplate/ScheduleField/ScheduleFieldStyles.js +13 -0
  37. package/src/commons/FieldTemplate/ScheduleField/__test__/index.test.js +182 -0
  38. package/src/commons/FieldTemplate/ScheduleField/index.js +176 -0
  39. package/src/commons/FullLoading/index.js +2 -1
  40. package/src/commons/MenuActionAddnew/index.js +1 -0
  41. package/src/commons/MenuActionList/index.js +1 -0
  42. package/src/commons/MenuActionMore/index.js +1 -1
  43. package/src/commons/PreventAccess/__test__/PreventAccess.test.js +62 -0
  44. package/src/commons/PreventAccess/index.js +67 -0
  45. package/src/commons/PreventAccess/styles.js +33 -0
  46. package/src/commons/WheelDateTimePicker/index.js +2 -1
  47. package/src/configs/API.js +3 -0
  48. package/src/configs/Constants.js +16 -1
  49. package/src/iot/RemoteControl/GoogleHome.js +24 -11
  50. package/src/iot/RemoteControl/__test__/GoogleHome.test.js +32 -0
  51. package/src/navigations/UnitStack.js +8 -0
  52. package/src/screens/AQIGuide/index.js +1 -1
  53. package/src/screens/ActivityLog/FilterPopup.js +2 -0
  54. package/src/screens/AddCommon/SelectSubUnit.js +1 -0
  55. package/src/screens/AddCommon/SelectUnit.js +1 -0
  56. package/src/screens/AddLocationMaps/index.js +4 -1
  57. package/src/screens/AddNewAction/SelectSensorDevices.js +14 -3
  58. package/src/screens/AddNewAction/__test__/SelectSensorDevices.test.js +34 -92
  59. package/src/screens/AddNewAutoSmart/__test__/AddNewAutoSmart.test.js +3 -1
  60. package/src/screens/AddNewAutoSmart/index.js +5 -2
  61. package/src/screens/AddNewDevice/index.js +1 -0
  62. package/src/screens/AddNewGateway/PlugAndPlay/ConnectWifiWarning.js +1 -1
  63. package/src/screens/AddNewGateway/PlugAndPlay/GatewayWifiList.js +4 -1
  64. package/src/screens/AddNewGateway/SelectGateway.js +1 -0
  65. package/src/screens/AddNewGateway/SetupGatewayWifi.js +1 -0
  66. package/src/screens/AddNewGateway/index.js +1 -0
  67. package/src/screens/AddNewOneTap/__test__/AddNewOneTap.test.js +1 -1
  68. package/src/screens/AddNewOneTap/index.js +3 -2
  69. package/src/screens/Automate/index.js +2 -0
  70. package/src/screens/Device/__test__/detail.test.js +4 -4
  71. package/src/screens/Device/detail.js +44 -6
  72. package/src/screens/EmergencyContacts/EmergencyContactsSelectContacts.js +5 -2
  73. package/src/screens/EmergencyContacts/__test__/EmergencyContactList.test.js +14 -0
  74. package/src/screens/EmergencyContacts/__test__/EmergencyContactsSelectContacts.test.js +19 -1
  75. package/src/screens/EmergencySetting/index.js +4 -1
  76. package/src/screens/Explore/index.js +2 -0
  77. package/src/screens/GuestInfo/__test__/index.test.js +1 -1
  78. package/src/screens/GuestInfo/components/RecurringDetail.js +1 -0
  79. package/src/screens/GuestInfo/components/TemporaryDetail.js +2 -2
  80. package/src/screens/ManageAccess/index.js +1 -0
  81. package/src/screens/MoveToAnotherSubUnit/index.js +1 -1
  82. package/src/screens/ScanChipQR/components/QRScan/index.js +1 -0
  83. package/src/screens/ScriptDetail/index.js +3 -3
  84. package/src/screens/SelectUnit/__test__/index.test.js +1 -1
  85. package/src/screens/SelectUnit/index.js +5 -2
  86. package/src/screens/SetSchedule/index.js +6 -2
  87. package/src/screens/SharedUnit/index.js +2 -0
  88. package/src/screens/Sharing/MemberList.js +12 -11
  89. package/src/screens/Sharing/SelectPermission.js +1 -1
  90. package/src/screens/Sharing/hooks/index.js +3 -0
  91. package/src/screens/SideMenuDetail/SideMenuDetailStyles.js +28 -0
  92. package/src/screens/SideMenuDetail/__test__/index.test.js +165 -0
  93. package/src/screens/SideMenuDetail/index.js +149 -0
  94. package/src/screens/SmartIr/components/SelectBrand.js +1 -1
  95. package/src/screens/SubUnit/ManageSubUnit.js +1 -0
  96. package/src/screens/SyncLGDevice/AddLGDevice.js +1 -0
  97. package/src/screens/TDSGuide/index.js +4 -1
  98. package/src/screens/UVIndexGuide/index.js +1 -1
  99. package/src/screens/Unit/Detail.js +18 -5
  100. package/src/screens/Unit/SelectAddress.js +4 -1
  101. package/src/screens/Unit/Station/index.js +1 -0
  102. package/src/screens/Unit/Summaries.js +1 -1
  103. package/src/screens/Unit/__test__/Detail.test.js +25 -5
  104. package/src/screens/UnitSummary/__test__/index.test.js +32 -0
  105. package/src/screens/UnitSummary/components/3PPowerConsumption/index.js +1 -1
  106. package/src/screens/WaterQualityGuide/index.js +1 -1
  107. package/src/utils/I18n/translations/en.json +5 -1
  108. package/src/utils/I18n/translations/vi.json +5 -1
  109. package/src/utils/Route/index.js +1 -0
@@ -75,7 +75,25 @@ describe('test EmergencyContactsSelectContacts', () => {
75
75
  test('test onSave emergencyContactsSelectContacts', async () => {
76
76
  const response = {
77
77
  status: 200,
78
- success: true,
78
+ };
79
+ axios.post.mockImplementation(async () => {
80
+ return response;
81
+ });
82
+ act(() => {
83
+ tree = create(wrapComponent(route));
84
+ });
85
+ const instance = tree.root;
86
+
87
+ const viewButtonBottom = instance.findByType(ViewButtonBottom);
88
+ await act(async () => {
89
+ viewButtonBottom.props.onRightClick();
90
+ });
91
+ expect(axios.post).toHaveBeenCalled();
92
+ });
93
+
94
+ test('test onSave emergencyContactsSelectContacts fail', async () => {
95
+ const response = {
96
+ status: 500,
79
97
  };
80
98
  axios.post.mockImplementation(async () => {
81
99
  return response;
@@ -63,7 +63,10 @@ const EmergencySetting = () => {
63
63
  <View style={styles.wrap}>
64
64
  <HeaderCustom title={t('setting')} isShowSeparator />
65
65
 
66
- <ScrollView contentContainerStyle={styles.contentContainerStyle}>
66
+ <ScrollView
67
+ contentContainerStyle={styles.contentContainerStyle}
68
+ scrollIndicatorInsets={{ right: 1 }}
69
+ >
67
70
  {listData.map((item, index) => (
68
71
  <DropDownItem
69
72
  {...item}
@@ -92,6 +92,7 @@ const Explore = ({ navigation }) => {
92
92
  horizontal={true}
93
93
  contentContainerStyle={styles.locationContent}
94
94
  showsHorizontalScrollIndicator={false}
95
+ scrollIndicatorInsets={{ right: 1 }}
95
96
  >
96
97
  {!!unitsNearMe &&
97
98
  unitsNearMe.map((item) => (
@@ -134,6 +135,7 @@ const Explore = ({ navigation }) => {
134
135
  onEndReachedThreshold={0.01}
135
136
  onMomentumScrollBegin={() => setOnEndReached(false)}
136
137
  onEndReached={handleEndReachUnitPublic}
138
+ scrollIndicatorInsets={{ right: 1 }}
137
139
  />
138
140
  </Animated.View>
139
141
  </SafeAreaView>
@@ -152,7 +152,7 @@ describe('Test GuestInfo', () => {
152
152
  );
153
153
  const dateTimePickerDone = getButton(
154
154
  instance,
155
- `${TESTID.WHEEL_DATE_TIME_PICKER}${TESTID.VIEW_BUTTON_BOTTOM_RIGHT_BUTTON}`
155
+ `${TESTID.WHEEL_DATE_TIME_PICKER_BUTTON}${TESTID.VIEW_BUTTON_BOTTOM_RIGHT_BUTTON}`
156
156
  );
157
157
 
158
158
  // show AccessScheduleSheet
@@ -47,6 +47,7 @@ const RecurringDetail = ({
47
47
  return (
48
48
  <TouchableOpacity
49
49
  key={index}
50
+ testID={`${index}${TESTID.RECURRING_REPEAT_ITEM}`}
50
51
  style={[styles.repeatItem, isSelected && styles.repeatItemSelected]}
51
52
  onPress={() => onSetRepeat(item)}
52
53
  >
@@ -30,7 +30,7 @@ const TemporaryDetail = ({
30
30
  </Text>
31
31
  <TouchableOpacity
32
32
  onPress={onSetTimeStart}
33
- testID={TESTID.TEMPORARY_TEXT_BUTTON}
33
+ testID={TESTID.TEMPORARY_START_TEXT_BUTTON}
34
34
  >
35
35
  <Text type="Body" color={Colors.Orange} style={styles.value}>
36
36
  {moment(temporaryTimeStart).format('hh:mm A DD/MM/YYYY')}
@@ -41,7 +41,7 @@ const TemporaryDetail = ({
41
41
  </Text>
42
42
  <TouchableOpacity
43
43
  onPress={onSetTimeEnd}
44
- testID={TESTID.TEMPORARY_TEXT_BUTTON}
44
+ testID={TESTID.TEMPORARY_END_TEXT_BUTTON}
45
45
  >
46
46
  <Text type="Body" color={Colors.Orange} style={styles.value}>
47
47
  {moment(temporaryTimeEnd).format('hh:mm A DD/MM/YYYY')}
@@ -46,6 +46,7 @@ const ManageAccessScreen = memo(({ route }) => {
46
46
  refreshControl={
47
47
  <RefreshControl refreshing={false} onRefresh={onRefresh} />
48
48
  }
49
+ scrollIndicatorInsets={{ right: 1 }}
49
50
  >
50
51
  <View style={styles.container}>
51
52
  <Text semibold style={styles.titleGuest}>
@@ -62,7 +62,7 @@ const MoveToAnotherSubUnit = memo(({ route }) => {
62
62
  return (
63
63
  <View style={styles.wrap}>
64
64
  <HeaderCustom title={t('move_to_another_sub_unit')} isShowSeparator />
65
- <ScrollView>
65
+ <ScrollView scrollIndicatorInsets={{ right: 1 }}>
66
66
  <View style={styles.container}>
67
67
  <View>
68
68
  {listStationUnit.map((item, index) => (
@@ -160,6 +160,7 @@ const styles = StyleSheet.create({
160
160
  position: 'absolute',
161
161
  top: getStatusBarHeight(true) + 16,
162
162
  left: 16,
163
+ zIndex: 10,
163
164
  },
164
165
  viewVerifing: {
165
166
  marginLeft: 16,
@@ -369,10 +369,10 @@ const ScriptDetail = ({ route }) => {
369
369
  return `${config_name} ${t(textCondition)} ${value}`;
370
370
  } else if (type === AUTOMATE_TYPE.SCHEDULE) {
371
371
  const time =
372
- time_repeat.length >= 8
373
- ? time_repeat.substring(0, time_repeat.length - 3)
372
+ time_repeat?.length >= 8
373
+ ? time_repeat.substring(0, time_repeat?.length - 3)
374
374
  : time_repeat;
375
- const date = date_repeat.split('-').reverse().join('/');
375
+ const date = date_repeat?.split('-').reverse().join('/');
376
376
  const weekday = {
377
377
  1: t('mon'),
378
378
  2: t('tue'),
@@ -235,7 +235,7 @@ describe('Test Select unit screen', () => {
235
235
  isCreateNewAction: true,
236
236
  isMultiUnits: undefined,
237
237
  name: '1',
238
- type: 'value_change',
238
+ type: undefined,
239
239
  unit: {
240
240
  id: 1,
241
241
  },
@@ -26,9 +26,10 @@ const SelectUnit = () => {
26
26
  scriptName,
27
27
  routeName,
28
28
  isCreateNewAction,
29
- unit,
30
29
  title,
31
30
  automate,
31
+ oldType,
32
+ unit,
32
33
  } = params;
33
34
  const [data, setData] = useState([]);
34
35
  const [selectedItem, setSelectedItem] = useState(data[0]);
@@ -49,7 +50,7 @@ const SelectUnit = () => {
49
50
  navigate(Routes.ScriptDetail, {
50
51
  id: automateId,
51
52
  name: scriptName,
52
- type: type,
53
+ type: oldType,
53
54
  havePermission: true,
54
55
  unit,
55
56
  isMultiUnits,
@@ -77,6 +78,7 @@ const SelectUnit = () => {
77
78
  unit: selectedItem,
78
79
  automateId,
79
80
  scriptName,
81
+ oldType,
80
82
  }
81
83
  );
82
84
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -155,6 +157,7 @@ const SelectUnit = () => {
155
157
  keyExtractor={(item) => item.id}
156
158
  data={data}
157
159
  renderItem={renderItem}
160
+ scrollIndicatorInsets={{ right: 1 }}
158
161
  />
159
162
  </View>
160
163
  </WrapHeaderScrollable>
@@ -36,6 +36,7 @@ const SetSchedule = ({ route }) => {
36
36
  automateId, // wilk=l remove later
37
37
  automate = {},
38
38
  scriptName,
39
+ oldType,
39
40
  } = route.params;
40
41
  const { navigate, dispatch, goBack } = useNavigation();
41
42
  const [repeat, setRepeat] = useState(automate.repeat || REPEAT_OPTIONS.ONCE);
@@ -119,7 +120,7 @@ const SetSchedule = ({ route }) => {
119
120
  navigate(Routes.ScriptDetail, {
120
121
  id: automateId,
121
122
  name: scriptName,
122
- type: type,
123
+ type: oldType,
123
124
  havePermission: true,
124
125
  unit,
125
126
  isMultiUnits,
@@ -136,7 +137,10 @@ const SetSchedule = ({ route }) => {
136
137
  <>
137
138
  <View style={styles.container}>
138
139
  <HeaderCustom isShowClose onClose={onClose} />
139
- <ScrollView contentContainerStyle={styles.scollView}>
140
+ <ScrollView
141
+ contentContainerStyle={styles.scollView}
142
+ scrollIndicatorInsets={{ right: 1 }}
143
+ >
140
144
  <Text type="H2" bold style={styles.title}>
141
145
  {t('set_schedule')}
142
146
  </Text>
@@ -139,6 +139,7 @@ const Shared = () => {
139
139
  keyExtractor={(item, index) => item.id.toString()}
140
140
  ItemSeparatorComponent={() => <View style={styles.seperator} />}
141
141
  contentContainerStyle={styles.scrollView}
142
+ scrollIndicatorInsets={{ right: 1 }}
142
143
  />
143
144
  ) : (
144
145
  <FlatList
@@ -162,6 +163,7 @@ const Shared = () => {
162
163
  keyExtractor={(item, index) => item.id.toString()}
163
164
  ItemSeparatorComponent={() => <View style={styles.seperator} />}
164
165
  contentContainerStyle={styles.scrollView}
166
+ scrollIndicatorInsets={{ right: 1 }}
165
167
  />
166
168
  )}
167
169
  </View>
@@ -21,10 +21,8 @@ const MemberList = ({ route }) => {
21
21
  const isFocused = useIsFocused();
22
22
  const account = useSCContextSelector((state) => state.auth.account);
23
23
  const { unitId, unit } = route.params;
24
- const { dataMembers, isRefresh, onRefresh, leaveUnit } = useDataMember(
25
- unitId,
26
- unit?.user_id
27
- );
24
+ const { dataMembers, isRefresh, onRefresh, leaveUnit, loading } =
25
+ useDataMember(unitId, unit?.user_id);
28
26
  const { isOwner } = useIsOwnerOfUnit(unit?.user_id);
29
27
 
30
28
  const { stateAlertSharingMenu, hideStateAlertSharingMenu, stateLeaveUnit } =
@@ -67,6 +65,7 @@ const MemberList = ({ route }) => {
67
65
  />
68
66
  </TouchableOpacity>
69
67
  );
68
+
70
69
  return (
71
70
  <View style={styles.container}>
72
71
  <WrapHeaderScrollable
@@ -77,13 +76,15 @@ const MemberList = ({ route }) => {
77
76
  headerAniStyle={styles.headerAniStyle}
78
77
  styleScrollView={{ backgroundColor: Colors.White }}
79
78
  >
80
- <SharingMembers
81
- testID={TESTID.SHARING_MEMBER}
82
- dataMember={dataMembers}
83
- unit={unit}
84
- ownerId={unit.user_id}
85
- currentUserId={account.user.id}
86
- />
79
+ {!loading && (
80
+ <SharingMembers
81
+ testID={TESTID.SHARING_MEMBER}
82
+ dataMember={dataMembers}
83
+ unit={unit}
84
+ ownerId={unit.user_id}
85
+ currentUserId={account.user.id}
86
+ />
87
+ )}
87
88
  </WrapHeaderScrollable>
88
89
  <AlertAction
89
90
  visible={stateAlertSharingMenu.visible}
@@ -225,7 +225,6 @@ const SelectPermission = ({ route }) => {
225
225
  ? member?.phone_number
226
226
  : member?.phone_number || '';
227
227
  const email = member?.phone_number ? '' : member?.email || '';
228
-
229
228
  const { success } = await axiosPost(API.SHARE.SHARE(), {
230
229
  phone,
231
230
  email,
@@ -266,6 +265,7 @@ const SelectPermission = ({ route }) => {
266
265
  />
267
266
  )
268
267
  }
268
+ scrollIndicatorInsets={{ right: 1 }}
269
269
  />
270
270
  )
271
271
  );
@@ -13,6 +13,7 @@ const useDataMember = (unitId, userUnitId = undefined) => {
13
13
  const { navigate, goBack } = useNavigation();
14
14
  const [dataMembers, setDataMembers] = useState([]);
15
15
  const [isRefresh, setRefresh] = useState(false);
16
+ const [loading, setLoading] = useState(true);
16
17
  const user = useSCContextSelector((state) => state?.auth?.account?.user);
17
18
  const { isOwner } = useIsOwnerOfUnit(userUnitId);
18
19
 
@@ -42,6 +43,7 @@ const useDataMember = (unitId, userUnitId = undefined) => {
42
43
  const arrChanged = changePositionOwnerAndMe(data);
43
44
  setDataMembers(arrChanged);
44
45
  }
46
+ setLoading(false);
45
47
  },
46
48
  [changePositionOwnerAndMe]
47
49
  );
@@ -82,6 +84,7 @@ const useDataMember = (unitId, userUnitId = undefined) => {
82
84
  loadMembers,
83
85
  isRefresh,
84
86
  onRefresh,
87
+ loading,
85
88
  };
86
89
  };
87
90
 
@@ -0,0 +1,28 @@
1
+ import { Colors } from '../../configs';
2
+ import { StyleSheet } from 'react-native';
3
+ import { getBottomSpace } from 'react-native-iphone-x-helper';
4
+
5
+ export default StyleSheet.create({
6
+ container: {
7
+ flex: 1,
8
+ backgroundColor: Colors.White,
9
+ },
10
+ scrollview: {
11
+ flex: 1,
12
+ marginHorizontal: 16,
13
+ },
14
+ wrap: {
15
+ paddingBottom: 100,
16
+ },
17
+ viewBottomFixed: {
18
+ position: 'absolute',
19
+ bottom: 0,
20
+ left: 0,
21
+ right: 0,
22
+ paddingBottom: getBottomSpace() > 0 ? getBottomSpace() : 32,
23
+ paddingTop: 24,
24
+ backgroundColor: Colors.White,
25
+ borderColor: Colors.ShadownTransparent,
26
+ borderTopWidth: 1,
27
+ },
28
+ });
@@ -0,0 +1,165 @@
1
+ import React from 'react';
2
+ import axios from 'axios';
3
+ import { act, create } from 'react-test-renderer';
4
+
5
+ import { TESTID } from '../../../configs/Constants';
6
+
7
+ import { SCProvider } from '../../../context';
8
+ import { mockSCStore } from '../../../context/mockStore';
9
+ import SideMenuDetail from '../index';
10
+ import { API } from '../../../configs';
11
+ import { TouchableOpacity } from 'react-native';
12
+ import { ModalCustom } from '../../../commons/Modal';
13
+ import { ToastBottomHelper } from '../../../utils/Utils';
14
+
15
+ jest.mock('axios');
16
+
17
+ const wrapComponent = (route) => (
18
+ <SCProvider initState={mockSCStore({})}>
19
+ <SideMenuDetail route={route} />
20
+ </SCProvider>
21
+ );
22
+
23
+ describe('Test SideMenuDetail', () => {
24
+ let tree;
25
+ const spyToastSuccess = jest.spyOn(ToastBottomHelper, 'success');
26
+ const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
27
+
28
+ afterEach(() => {
29
+ axios.get.mockClear();
30
+ axios.post.mockClear();
31
+ spyToastSuccess.mockReset();
32
+ spyToastSuccess.mockRestore();
33
+ });
34
+
35
+ const chooseUser = async (instance) => {
36
+ const ChooseField = instance.find(
37
+ (item) =>
38
+ item.props.testID === TESTID.CHOOSE_FIELD &&
39
+ item.type === TouchableOpacity
40
+ );
41
+ await act(async () => {
42
+ await ChooseField.props.onPress();
43
+ });
44
+
45
+ const popup = instance.find(
46
+ (item) =>
47
+ item.props.testID === TESTID.CHOOSE_POPUP && item.type === ModalCustom
48
+ );
49
+ expect(popup.props.isVisible).toBeTruthy();
50
+
51
+ const button_choose_done = instance.find(
52
+ (el) =>
53
+ el.props.testID === TESTID.CHOOSE_POPUP + TESTID.BOTTOM_VIEW_MAIN &&
54
+ el.type === TouchableOpacity
55
+ );
56
+
57
+ await act(async () => {
58
+ await button_choose_done.props.onPress();
59
+ });
60
+ expect(popup.props.isVisible).toBeFalsy();
61
+ };
62
+
63
+ const inputPasscode = async (instance) => {
64
+ const inputPasscode = instance.find(
65
+ (item) => item.props.testID === TESTID.PASSCODE_FIELD
66
+ );
67
+
68
+ await act(async () => {
69
+ await inputPasscode.props.onChangeText('123456');
70
+ });
71
+ };
72
+
73
+ const onPressSubmitData = async (instance) => {
74
+ const buttonSubmit = instance.find(
75
+ (el) =>
76
+ el.props.testID === TESTID.SUBMIT + TESTID.BOTTOM_VIEW_MAIN &&
77
+ el.type === TouchableOpacity
78
+ );
79
+ await act(async () => {
80
+ await buttonSubmit.props.onPress();
81
+ });
82
+ };
83
+
84
+ test('render SideMenuDetail template choose field', async () => {
85
+ const response_side_menu = {
86
+ status: 200,
87
+ data: {
88
+ id: 1,
89
+ action: '3df74a11-1a95-4a65-8e08-936b88c8b5ca',
90
+ fields: [
91
+ {
92
+ type: 'choose_user',
93
+ key: 'user_id',
94
+ action: {
95
+ type: 'action_zigbee',
96
+ id: 1,
97
+ },
98
+ },
99
+ {
100
+ type: 'passcode',
101
+ key: 'passcode',
102
+ action: {
103
+ type: 'action_zigbee',
104
+ id: 1,
105
+ },
106
+ },
107
+ {
108
+ type: 'schedule',
109
+ key: 'schedule',
110
+ action: {
111
+ type: 'action_zigbee',
112
+ id: {
113
+ always: 2,
114
+ recurring: 4,
115
+ temporary: 5,
116
+ },
117
+ },
118
+ },
119
+ ],
120
+ },
121
+ };
122
+ const response_unit_members = {
123
+ status: 200,
124
+ data: [{ id: 1, name: 'Ken' }],
125
+ };
126
+
127
+ axios.get.mockImplementation((url) => {
128
+ if (url === API.SENSOR.SIDE_MENU_DETAIL(1, 1)) {
129
+ return response_side_menu;
130
+ } else if (url === API.SHARE.UNITS_MEMBERS(1)) {
131
+ return response_unit_members;
132
+ }
133
+ return {
134
+ status: 200,
135
+ data: [],
136
+ };
137
+ });
138
+
139
+ axios.post.mockImplementation(() => {
140
+ return {
141
+ status: 200,
142
+ data: [],
143
+ };
144
+ });
145
+ const route = {
146
+ params: {
147
+ unit: { id: 1 },
148
+ sensor: { id: 1 },
149
+ side_menu: { id: 1, name: 'Setup generate pass code' },
150
+ },
151
+ };
152
+ await act(async () => {
153
+ tree = await create(wrapComponent(route));
154
+ });
155
+
156
+ const instance = tree.root;
157
+ await chooseUser(instance);
158
+ await onPressSubmitData(instance);
159
+ expect(spyToastError).toBeCalled();
160
+
161
+ await inputPasscode(instance);
162
+ await onPressSubmitData(instance);
163
+ expect(spyToastSuccess).toBeCalled();
164
+ });
165
+ });
@@ -0,0 +1,149 @@
1
+ import React, { memo, useCallback, useEffect, useState } from 'react';
2
+ import { View, ScrollView } from 'react-native';
3
+ import { useTranslations } from '../../hooks/Common/useTranslations';
4
+
5
+ import styles from './SideMenuDetailStyles';
6
+ import { HeaderCustom } from '../../commons/Header';
7
+ import BottomButtonView from '../../commons/BottomButtonView';
8
+ import { axiosGet, axiosPost } from '../../utils/Apis/axios';
9
+ import { API } from '../../configs';
10
+ import PasscodeField from '../../commons/FieldTemplate/PasscodeField';
11
+ import ChooseUserField from '../../commons/FieldTemplate/ChooseUserField';
12
+ import ScheduleField from '../../commons/FieldTemplate/ScheduleField';
13
+ import { TESTID } from '../../configs/Constants';
14
+ import { ToastBottomHelper } from '../../utils/Utils';
15
+ import { useNavigation } from '@react-navigation/native';
16
+
17
+ const SideMenuDetail = memo(({ route }) => {
18
+ const t = useTranslations();
19
+ const { goBack } = useNavigation();
20
+ const { unit, sensor, side_menu } = route?.params || {};
21
+ const [sideMenu, setSideMenu] = useState({});
22
+ const [dataForm, setDataForm] = useState([]);
23
+
24
+ const renderTemplateField = useCallback(
25
+ (item, index) => {
26
+ const keyItem = item.key;
27
+ const dataItem = item;
28
+ switch (item.type) {
29
+ case 'choose_user':
30
+ return (
31
+ <ChooseUserField
32
+ key={index} // Todo avoid waring unique "key" prop
33
+ unit={unit}
34
+ dataItem={dataItem}
35
+ index={index}
36
+ setDataForm={setDataForm}
37
+ dataForm={dataForm}
38
+ />
39
+ );
40
+ case 'passcode':
41
+ return (
42
+ <PasscodeField
43
+ key={index}
44
+ dataItem={dataItem}
45
+ index={index}
46
+ setDataForm={setDataForm}
47
+ dataForm={dataForm}
48
+ />
49
+ );
50
+ case 'schedule':
51
+ return (
52
+ <ScheduleField
53
+ key={index}
54
+ dataItem={dataItem}
55
+ keyItem={keyItem}
56
+ setDataForm={setDataForm}
57
+ dataForm={dataForm}
58
+ />
59
+ );
60
+ default:
61
+ return null;
62
+ }
63
+ },
64
+ // eslint-disable-next-line react-hooks/exhaustive-deps
65
+ [unit, dataForm]
66
+ );
67
+
68
+ const onPressSubmit = useCallback(async () => {
69
+ const data = {};
70
+ const actionZigbee = [];
71
+ for (let i = 0; i < dataForm.length; i++) {
72
+ if (!dataForm[i].valid) {
73
+ ToastBottomHelper.error(t('invaild_data'));
74
+ return;
75
+ }
76
+ if (dataForm[i].data) {
77
+ data[dataForm[i].key] = dataForm[i].data;
78
+ if (
79
+ dataForm[i].action_zigbee &&
80
+ !actionZigbee.includes(dataForm[i].action_zigbee)
81
+ ) {
82
+ actionZigbee.push(dataForm[i].action_zigbee);
83
+ }
84
+ } else {
85
+ data[dataForm[i].key] = null;
86
+ }
87
+ }
88
+
89
+ data.action_zigbee = actionZigbee;
90
+
91
+ const { success } = await axiosPost(API.SENSOR.QUICK_ACTION(sensor.id), {
92
+ key: sideMenu.action,
93
+ data,
94
+ source: 'smart_lock',
95
+ });
96
+
97
+ if (success) {
98
+ ToastBottomHelper.success(t('activated_successfully'));
99
+ goBack();
100
+ } else {
101
+ ToastBottomHelper.error(t('activation_failed'));
102
+ }
103
+ // eslint-disable-next-line react-hooks/exhaustive-deps
104
+ }, [dataForm]);
105
+
106
+ const fetchSideMenuDetail = useCallback(async () => {
107
+ const { success, data } = await axiosGet(
108
+ API.SENSOR.SIDE_MENU_DETAIL(sensor.id, side_menu.id)
109
+ );
110
+ if (success) {
111
+ setSideMenu(data);
112
+ setDataForm(data.fields);
113
+ }
114
+ }, [sensor.id, side_menu.id]);
115
+
116
+ useEffect(() => {
117
+ fetchSideMenuDetail();
118
+ // eslint-disable-next-line react-hooks/exhaustive-deps
119
+ }, []);
120
+
121
+ return (
122
+ <View style={styles.container}>
123
+ <HeaderCustom
124
+ title={side_menu.name}
125
+ titleStyle={styles.title}
126
+ isShowSeparator
127
+ />
128
+ <ScrollView
129
+ showsVerticalScrollIndicator={false}
130
+ showsHorizontalScrollIndicator={false}
131
+ style={styles.scrollview}
132
+ >
133
+ <View style={styles.wrap}>
134
+ {dataForm.map((item, index) => {
135
+ return renderTemplateField(item, index);
136
+ })}
137
+ </View>
138
+ </ScrollView>
139
+ <BottomButtonView
140
+ style={styles.viewBottomFixed}
141
+ testIDPrefix={TESTID.SUBMIT}
142
+ mainTitle={t('done')}
143
+ onPressMain={onPressSubmit}
144
+ />
145
+ </View>
146
+ );
147
+ });
148
+
149
+ export default SideMenuDetail;
@@ -36,7 +36,7 @@ const SelectDeviceType = memo(({ route }) => {
36
36
  return (
37
37
  <View style={styles.container}>
38
38
  <HeaderCustom title={t('select_brand')} />
39
- <ScrollView>
39
+ <ScrollView scrollIndicatorInsets={{ right: 1 }}>
40
40
  <View style={styles.listBrands}>
41
41
  {!!deviceBrand &&
42
42
  deviceBrand.map((item, index) => {
@@ -81,6 +81,7 @@ const ManageSubUnit = memo((props) => {
81
81
  refreshControl={
82
82
  <RefreshControl refreshing={false} onRefresh={onRefresh} />
83
83
  }
84
+ scrollIndicatorInsets={{ right: 1 }}
84
85
  >
85
86
  <View>
86
87
  {!!station.length &&