@eohjsc/react-native-smart-city 0.2.91 → 0.2.94

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 (115) hide show
  1. package/assets/images/Device/air-dry.svg +3 -0
  2. package/assets/images/Device/auto.svg +3 -0
  3. package/assets/images/Device/circulator.svg +4 -0
  4. package/assets/images/Device/clean.svg +5 -0
  5. package/assets/images/Device/current-state.svg +3 -0
  6. package/assets/images/Device/door-state.svg +3 -0
  7. package/assets/images/Device/wind-strength.svg +12 -0
  8. package/package.json +1 -1
  9. package/src/commons/Action/ItemQuickAction.js +1 -0
  10. package/src/commons/Action/__test__/ItemQuickAction.test.js +49 -2
  11. package/src/commons/ActionGroup/ColorPickerTemplate.js +30 -24
  12. package/src/commons/ActionGroup/OnOffSmartLock/OnOffSmartLock.js +60 -12
  13. package/src/commons/ActionGroup/OnOffSmartLock/PasscodeList/__test__/index.test.js +47 -0
  14. package/src/commons/ActionGroup/OnOffSmartLock/PasscodeList/index.js +2 -0
  15. package/src/commons/ActionGroup/OnOffTemplate/OnOffSimpleTemplateStyle.js +2 -1
  16. package/src/commons/ActionGroup/OnOffTemplate/index.js +48 -28
  17. package/src/commons/ActionGroup/OptionsDropdownActionTemplate.js +31 -11
  18. package/src/commons/ActionGroup/OptionsDropdownActionTemplateStyle.js +5 -2
  19. package/src/commons/ActionGroup/SliderRangeTemplate.js +19 -5
  20. package/src/commons/ActionGroup/StatesGridActionTemplate.js +42 -36
  21. package/src/commons/ActionGroup/StatesGridActionTemplateStyle.js +5 -10
  22. package/src/commons/ActionGroup/TimerActionTemplate.js +14 -10
  23. package/src/commons/ActionGroup/TimerActionTemplateStyles.js +12 -0
  24. package/src/commons/ActionGroup/TwoButtonTemplate/TwoButtonTemplateStyles.js +55 -0
  25. package/src/commons/ActionGroup/TwoButtonTemplate/index.js +170 -0
  26. package/src/commons/ActionGroup/__test__/ColorPickerTemplate.test.js +74 -0
  27. package/src/commons/ActionGroup/__test__/OnOffSmartLock.test.js +107 -0
  28. package/src/commons/ActionGroup/__test__/SliderRangeTemplate.test.js +71 -0
  29. package/src/commons/ActionGroup/__test__/TimerActionTemplate.test.js +1 -1
  30. package/src/commons/ActionGroup/__test__/TwoButtonTemplate.test.js +112 -0
  31. package/src/commons/ActionGroup/index.js +4 -1
  32. package/src/commons/Calendar/index.js +5 -1
  33. package/src/commons/CameraDevice/index.js +6 -1
  34. package/src/commons/ConnectingProcess/__test__/Connecting.test.js +4 -1
  35. package/src/commons/Device/HistoryChart.js +2 -2
  36. package/src/commons/Device/HorizontalBarChart.js +6 -2
  37. package/src/commons/Device/ItemDevice.js +3 -13
  38. package/src/commons/Explore/SearchBox/__test__/SearchBox.test.js +19 -0
  39. package/src/commons/Explore/SearchBox/index.js +2 -0
  40. package/src/commons/IconComponent/index.js +32 -26
  41. package/src/commons/MediaPlayerDetail/index.js +16 -4
  42. package/src/commons/MenuActionMore/index.js +14 -1
  43. package/src/commons/Modal/ModalCustom.js +9 -1
  44. package/src/commons/SubUnit/Favorites/index.js +11 -41
  45. package/src/commons/SubUnit/ShortDetail.js +7 -41
  46. package/src/commons/SubUnit/__test__/Favorites.test.js +33 -35
  47. package/src/configs/API.js +4 -0
  48. package/src/configs/Colors.js +1 -0
  49. package/src/configs/Constants.js +29 -0
  50. package/src/context/actionType.ts +17 -0
  51. package/src/context/mockStore.ts +18 -0
  52. package/src/context/reducer.ts +102 -0
  53. package/src/hooks/Common/index.js +2 -0
  54. package/src/hooks/Common/useSensorsStatus.js +52 -0
  55. package/src/iot/RemoteControl/Bluetooth.js +2 -0
  56. package/src/iot/RemoteControl/GoogleHome.js +1 -0
  57. package/src/navigations/AutomateStack.js +16 -1
  58. package/src/navigations/UnitStack.js +27 -0
  59. package/src/screens/AddCommon/SelectSubUnit.js +1 -0
  60. package/src/screens/AddCommon/__test__/SelectSubUnit.test.js +2 -0
  61. package/src/screens/AddNewAction/Device/__test__/index.test.js +1 -1
  62. package/src/screens/AddNewAction/SelectAction.js +13 -15
  63. package/src/screens/AddNewAction/__test__/SelectAction.test.js +0 -7
  64. package/src/screens/AddNewGateway/PlugAndPlay/ConnectWifiWarning.js +2 -0
  65. package/src/screens/AddNewGateway/PlugAndPlay/GatewayWifiList.js +2 -0
  66. package/src/screens/AddNewGateway/PlugAndPlay/__test__/ConnectWifiWarning.test.js +10 -0
  67. package/src/screens/AddNewGateway/PlugAndPlay/__test__/GatewayWifiList.test.js +15 -0
  68. package/src/screens/AddNewGateway/SetupGatewayWifi.js +6 -1
  69. package/src/screens/AddNewGateway/__test__/SelectGateway.test.js +61 -0
  70. package/src/screens/AddNewGateway/__test__/SetupGateway.test.js +34 -0
  71. package/src/screens/AllCamera/index.js +1 -0
  72. package/src/screens/Automate/MultiUnits.js +9 -9
  73. package/src/screens/Automate/index.js +21 -20
  74. package/src/screens/Device/__test__/DetailHistoryChart.test.js +40 -0
  75. package/src/screens/Device/__test__/detail.test.js +119 -86
  76. package/src/screens/Device/detail.js +38 -51
  77. package/src/screens/Device/hooks/useFavoriteDevice.js +38 -0
  78. package/src/screens/EmergencyContacts/EmergencyContactsList.js +1 -1
  79. package/src/screens/EmergencyContacts/EmergencyContactsSelectContacts.js +41 -44
  80. package/src/screens/EmergencyContacts/__test__/EmergencyContactList.test.js +1 -0
  81. package/src/screens/EmergencyContacts/__test__/EmergencyContactsSelectContacts.test.js +18 -19
  82. package/src/screens/HanetCamera/Detail.js +20 -13
  83. package/src/screens/HanetCamera/ManageAccess.js +10 -52
  84. package/src/screens/HanetCamera/MemberInfo.js +59 -13
  85. package/src/screens/HanetCamera/__test__/ManageAccess.test.js +19 -0
  86. package/src/screens/HanetCamera/__test__/MemberInfo.test.js +57 -10
  87. package/src/screens/HanetCamera/components/RequestFaceIDPopup.js +90 -0
  88. package/src/screens/HanetCamera/hooks/__test__/useHanetCheckinData.test.js +9 -12
  89. package/src/screens/HanetCamera/hooks/useHanetCheckinData.js +10 -7
  90. package/src/screens/HanetCamera/styles/manageAccessStyles.js +0 -14
  91. package/src/screens/MoveToAnotherSubUnit/index.js +3 -1
  92. package/src/screens/Notification/__test__/NotificationItem.test.js +64 -53
  93. package/src/screens/Notification/components/NotificationItem.js +13 -4
  94. package/src/screens/ScriptDetail/__test__/index.test.js +15 -4
  95. package/src/screens/ScriptDetail/hooks/useStarredScript.js +32 -0
  96. package/src/screens/ScriptDetail/index.js +11 -20
  97. package/src/screens/SharedUnit/__test__/TabHeader.test.js +5 -0
  98. package/src/screens/Sharing/SelectUser.js +3 -23
  99. package/src/screens/Sharing/__test__/SelectUser.test.js +12 -80
  100. package/src/screens/SmartIr/__test__/GroupButtonByType.test.js +33 -0
  101. package/src/screens/SmartIr/components/GroupButtonByType/GroupButtonByType.js +2 -0
  102. package/src/screens/Unit/ChooseLocation.js +5 -0
  103. package/src/screens/Unit/Detail.js +34 -44
  104. package/src/screens/Unit/ManageUnit.js +21 -20
  105. package/src/screens/Unit/ManageUnitStyles.js +1 -0
  106. package/src/screens/Unit/SelectAddress.js +8 -2
  107. package/src/screens/Unit/Summaries.js +12 -15
  108. package/src/screens/Unit/__test__/Detail.test.js +25 -0
  109. package/src/screens/Unit/components/MyUnitDevice/index.js +13 -10
  110. package/src/screens/Unit/components/__test__/Header.test.js +32 -0
  111. package/src/screens/Unit/components/__test__/MyUnitDevice.test.js +2 -2
  112. package/src/screens/Unit/hook/useFavorites.js +28 -0
  113. package/src/utils/Apis/axios.js +7 -2
  114. package/src/utils/I18n/translations/en.json +3 -5
  115. package/src/utils/I18n/translations/vi.json +2 -4
@@ -0,0 +1,90 @@
1
+ import React from 'react';
2
+ import { TouchableOpacity, StyleSheet } from 'react-native';
3
+ import ImagePicker from 'react-native-image-crop-picker';
4
+ import { IconFill } from '@ant-design/icons-react-native';
5
+ import Text from '../../../commons/Text';
6
+ import BottomSheet from '../../../commons/BottomSheet';
7
+ import { Colors } from '../../../configs';
8
+ import { useTranslations } from '../../../hooks/Common/useTranslations';
9
+ import SmartPhoneSvg from '../../../../assets/images/Common/SmartPhone.svg';
10
+ import { TESTID } from '../../../configs/Constants';
11
+
12
+ const RequestFaceIDPopup = ({
13
+ title,
14
+ isVisible,
15
+ setHide,
16
+ onCaptureFaceID,
17
+ onChooseFile,
18
+ }) => {
19
+ const t = useTranslations();
20
+ const options = [
21
+ {
22
+ icon: <IconFill name="camera" color={Colors.Gray9} size={27} />,
23
+ text: t('capture_image'),
24
+ onChoose: () => {
25
+ setHide();
26
+ onCaptureFaceID && onCaptureFaceID();
27
+ },
28
+ },
29
+ {
30
+ icon: <SmartPhoneSvg />,
31
+ text: t('pick_available_image_from_your_phone'),
32
+ onChoose: async () => {
33
+ setHide();
34
+ const options = {
35
+ mediaType: 'photo',
36
+ compressImageMaxHeight: 1280,
37
+ compressImageMaxWidth: 738,
38
+ compressImageQuality: 0.7,
39
+ forceJpg: true,
40
+ };
41
+ try {
42
+ const result = await ImagePicker.openPicker(options);
43
+ onChooseFile && onChooseFile(result);
44
+ } catch (e) {}
45
+ },
46
+ },
47
+ ];
48
+
49
+ return (
50
+ <BottomSheet
51
+ isVisible={isVisible}
52
+ onBackdropPress={setHide}
53
+ title={title}
54
+ style={styles.wrap}
55
+ >
56
+ {options.map((option, i) => (
57
+ <TouchableOpacity
58
+ key={i}
59
+ onPress={option.onChoose}
60
+ style={styles.row}
61
+ testID={`${TESTID.HANET_ADD_MEMBER_OPTION}_${i}`}
62
+ >
63
+ {option.icon}
64
+ <Text type="H4" color={Colors.Gray9} style={styles.textOption}>
65
+ {option.text}
66
+ </Text>
67
+ </TouchableOpacity>
68
+ ))}
69
+ </BottomSheet>
70
+ );
71
+ };
72
+
73
+ export default RequestFaceIDPopup;
74
+
75
+ const styles = StyleSheet.create({
76
+ wrap: {
77
+ paddingBottom: 50,
78
+ },
79
+ row: {
80
+ flexDirection: 'row',
81
+ alignItems: 'center',
82
+ paddingVertical: 16,
83
+ marginHorizontal: 16,
84
+ borderBottomColor: Colors.Gray3,
85
+ borderBottomWidth: 1,
86
+ },
87
+ textOption: {
88
+ marginLeft: 18,
89
+ },
90
+ });
@@ -38,6 +38,13 @@ describe('Test useHanetCheckinData', () => {
38
38
  detected_mask: 'MASK_ON',
39
39
  created_at: moment(),
40
40
  },
41
+ {
42
+ id: 3,
43
+ person_type: 'IMAGE_FROM_CAMERA',
44
+ detected_image_uri: 'uri',
45
+ detected_mask: 'NOT_HANDLE',
46
+ created_at: moment(),
47
+ },
41
48
  ],
42
49
  };
43
50
  });
@@ -129,19 +136,9 @@ describe('Test useHanetCheckinData', () => {
129
136
  expect(result.current.countMember).toBe(1);
130
137
  expect(result.current.countStranger).toBe(1);
131
138
 
139
+ // send old data
132
140
  await act(async () => {
133
- await result.current.onChangeDate(moment().add(-1, 'days'));
134
- });
135
- const newData4 = {
136
- id: 4,
137
- person_name: 'name 2',
138
- person_type: 'STRANGER',
139
- detected_image_uri: 'uri',
140
- detected_mask: 'MASK_ON',
141
- created_at: moment(),
142
- };
143
- await act(async () => {
144
- await result.current.onReceiveNewCheckinData(newData4);
141
+ await result.current.onReceiveNewCheckinData(newData2);
145
142
  });
146
143
  // no change
147
144
  expect(result.current.checkinData).toHaveLength(2);
@@ -48,7 +48,7 @@ const useHanetCheckinData = (hanetCamera) => {
48
48
  } else {
49
49
  setCanLoadMore(page < Math.ceil(data.count / 20));
50
50
  setCheckinData((prevData) =>
51
- _.uniqBy(prevData.concat(data.results || []))
51
+ _.uniqBy(prevData.concat(data.results || []), 'id')
52
52
  );
53
53
  }
54
54
  }
@@ -80,17 +80,20 @@ const useHanetCheckinData = (hanetCamera) => {
80
80
  };
81
81
 
82
82
  const onReceiveNewCheckinData = (data) => {
83
- if (!moment(data.created_at).isSame(date, 'date')) {
83
+ if (!moment(data.created_at).isSame(moment(), 'date')) {
84
84
  return;
85
85
  }
86
86
  setCheckinData((listData) => {
87
+ const existed = listData.find((i) => i.id === data.id);
88
+ if (!existed) {
89
+ if (['EMPLOYEE', 'CUSTOMER'].includes(data.person_type)) {
90
+ setCountMember((prevData) => prevData + 1);
91
+ } else {
92
+ setCountStranger((prevData) => prevData + 1);
93
+ }
94
+ }
87
95
  return _.uniqBy([data, ...listData], 'id');
88
96
  });
89
- if (['EMPLOYEE', 'CUSTOMER'].includes(data.person_type)) {
90
- setCountMember((prevData) => prevData + 1);
91
- } else {
92
- setCountStranger((prevData) => prevData + 1);
93
- }
94
97
  };
95
98
 
96
99
  useEffect(() => {
@@ -32,18 +32,4 @@ export default StyleSheet.create({
32
32
  marginLeft: 16,
33
33
  flex: 1,
34
34
  },
35
- addMemberModal: {
36
- paddingBottom: 50,
37
- },
38
- row: {
39
- flexDirection: 'row',
40
- alignItems: 'center',
41
- paddingVertical: 16,
42
- marginHorizontal: 16,
43
- borderBottomColor: Colors.Gray3,
44
- borderBottomWidth: 1,
45
- },
46
- textOption: {
47
- marginLeft: 18,
48
- },
49
35
  });
@@ -35,7 +35,9 @@ const MoveToAnotherSubUnit = memo(({ route }) => {
35
35
  const { params = {} } = route;
36
36
  const { unit, sensor, station } = params;
37
37
  const { navigate } = useNavigation();
38
- const [selectedSubUnit, setSelectedSubUnit] = useState(station);
38
+ const [selectedSubUnit, setSelectedSubUnit] = useState(
39
+ unit.stations?.find((subUnit) => subUnit.id === station.id)
40
+ );
39
41
 
40
42
  const listStationUnit = useMemo(() => {
41
43
  return unit.stations.slice(2);
@@ -36,7 +36,7 @@ describe('test NotificationItem', () => {
36
36
  id: 1,
37
37
  content_code: '',
38
38
  is_read: true,
39
- params: JSON.stringify({ booking_id: 1, booking_id_new: 1 }),
39
+ params: "{'booking_id': 1, 'booking_id_new': 1}",
40
40
  created_at: '',
41
41
  icon: '',
42
42
  };
@@ -49,62 +49,47 @@ describe('test NotificationItem', () => {
49
49
  {
50
50
  content_code: NOTIFICATION_TYPES.REMIND_TO_MAKE_PAYMENT,
51
51
  screen: Routes.SmartParkingBookingDetails,
52
- params: { id: 1 },
52
+ params: "{ 'id': 1 }",
53
53
  },
54
54
  {
55
55
  content_code: NOTIFICATION_TYPES.EXPIRE_PARKING_SESSION,
56
56
  screen: Routes.SmartParkingBookingDetails,
57
- params: { id: 1 },
57
+ params: "{ 'id': 1 }",
58
58
  },
59
59
  {
60
60
  content_code: NOTIFICATION_TYPES.REMIND_TO_SCAN_QR_CODE,
61
61
  screen: Routes.SmartParkingBookingDetails,
62
- params: { id: 1 },
63
- },
64
- {
65
- content_code: NOTIFICATION_TYPES.USER_CANCEL,
66
- screen: Routes.MyBookingList,
67
- params: { tab: 1 },
68
- },
69
- {
70
- content_code: NOTIFICATION_TYPES.SYSTEM_CANCEL_NO_PAYMENT,
71
- screen: Routes.MyBookingList,
72
- params: { tab: 1 },
62
+ params: "{ 'id': 1 }",
73
63
  },
74
64
  {
75
65
  content_code: NOTIFICATION_TYPES.BOOKING_SUCCESSFULLY,
76
66
  screen: Routes.SmartParkingBookingDetails,
77
- params: { id: 1 },
78
- },
79
- {
80
- content_code: NOTIFICATION_TYPES.PARKING_COMPLETED,
81
- screen: Routes.MyBookingList,
82
- params: { tab: 1 },
67
+ params: "{ 'id': 1 }",
83
68
  },
84
69
  {
85
70
  content_code: NOTIFICATION_TYPES.BOOKING_EXPIRED_AND_VIOLATION_CREATED,
86
71
  screen: Routes.SmartParkingBookingDetails,
87
- params: { id: 1 },
72
+ params: "{ 'id': 1 }",
88
73
  },
89
74
  {
90
75
  content_code: NOTIFICATION_TYPES.MOVE_CAR_WITHOUT_PAY_VIOLATION,
91
76
  screen: Routes.SmartParkingBookingDetails,
92
- params: { id: 1 },
77
+ params: "{ 'id': 1 }",
93
78
  },
94
79
  {
95
80
  content_code: NOTIFICATION_TYPES.PAY_FINE_SUCCESSFULLY,
96
81
  screen: Routes.SmartParkingBookingDetails,
97
- params: { id: 1 },
82
+ params: "{ 'id': 1 }",
98
83
  },
99
84
  {
100
85
  content_code: NOTIFICATION_TYPES.STOP_VIOLATION_FREE_PARKING_ZONE,
101
86
  screen: Routes.SmartParkingBookingDetails,
102
- params: { id: 1 },
87
+ params: "{ 'id': 1 }",
103
88
  },
104
89
  {
105
90
  content_code: NOTIFICATION_TYPES.PAY_FINE_AND_EXTEND_SUCCESSFULLY,
106
91
  screen: Routes.SmartParkingBookingDetails,
107
- params: { id: 1 },
92
+ params: "{ 'id': 1 }",
108
93
  },
109
94
  ];
110
95
 
@@ -121,7 +106,42 @@ describe('test NotificationItem', () => {
121
106
  });
122
107
  expect(mockNavigate).toHaveBeenCalledWith(Routes.SmartParkingStack, {
123
108
  screen: notify.screen,
124
- params: notify.params,
109
+ params: { id: 1 },
110
+ });
111
+ });
112
+ }
113
+
114
+ const listCaseSmartParking2 = [
115
+ {
116
+ content_code: NOTIFICATION_TYPES.USER_CANCEL,
117
+ screen: Routes.MyBookingList,
118
+ params: "{ 'tab': 1 }",
119
+ },
120
+ {
121
+ content_code: NOTIFICATION_TYPES.SYSTEM_CANCEL_NO_PAYMENT,
122
+ screen: Routes.MyBookingList,
123
+ params: "{ 'tab': 1 }",
124
+ },
125
+ {
126
+ content_code: NOTIFICATION_TYPES.PARKING_COMPLETED,
127
+ screen: Routes.MyBookingList,
128
+ params: "{ 'tab': 1 }",
129
+ },
130
+ ];
131
+ for (const notify of listCaseSmartParking2) {
132
+ test(`create ItemNotification ${notify.content_code}`, () => {
133
+ item.content_code = notify.content_code;
134
+ act(() => {
135
+ tree = create(wrapComponent(item));
136
+ });
137
+ const instance = tree.root;
138
+ const button = instance.findByType(TouchableOpacity);
139
+ act(() => {
140
+ button.props.onPress();
141
+ });
142
+ expect(mockNavigate).toHaveBeenCalledWith(Routes.SmartParkingStack, {
143
+ screen: notify.screen,
144
+ params: { tab: 1 },
125
145
  });
126
146
  });
127
147
  }
@@ -160,15 +180,7 @@ describe('test NotificationItem', () => {
160
180
  for (const sensorType of listSensorType) {
161
181
  test(`create ItemNotification NOTIFY_INDICATOR sensor_type ${sensorType}`, () => {
162
182
  item.content_code = NOTIFICATION_TYPES.NOTIFY_INDICATOR;
163
- item.params = JSON.stringify({
164
- unit_name: 'EoH Office',
165
- status: 'Very Poor',
166
- sensor_type: sensorType,
167
- unit_id: 5,
168
- summary_id: 11,
169
- sensor_id: '',
170
- });
171
-
183
+ item.params = `{'sensor_type': '${sensorType}', 'unit_id': 5, 'summary_id': 11}`;
172
184
  act(() => {
173
185
  tree = create(wrapComponent(item));
174
186
  });
@@ -196,14 +208,7 @@ describe('test NotificationItem', () => {
196
208
  for (const sensorType of listSensorType2) {
197
209
  test(`create ItemNotification NOTIFY_INDICATOR sensor_type ${sensorType}`, () => {
198
210
  item.content_code = NOTIFICATION_TYPES.NOTIFY_INDICATOR;
199
- item.params = JSON.stringify({
200
- unit_name: 'Lavida Smart Home',
201
- status: '',
202
- sensor_type: sensorType,
203
- unit_id: 70,
204
- summary_id: '',
205
- sensor_id: 394,
206
- });
211
+ item.params = `{'sensor_type': '${sensorType}', 'unit_id': 70, 'sensor_id': 394}`;
207
212
 
208
213
  act(() => {
209
214
  tree = create(wrapComponent(item));
@@ -225,14 +230,8 @@ describe('test NotificationItem', () => {
225
230
 
226
231
  test('test onClick Item Notify', () => {
227
232
  item.content_code = NOTIFICATION_TYPES.NOTIFY_INDICATOR;
228
- item.params = JSON.stringify({
229
- unit_name: 'EoH Office',
230
- status: 'Very Poor',
231
- sensor_type: 'air_quality',
232
- unit_id: 5,
233
- summary_id: 11,
234
- sensor_id: '',
235
- });
233
+ item.params =
234
+ "{'sensor_type': 'air_quality', 'unit_id': 5, 'summary_id': 11}";
236
235
  item.is_read = false;
237
236
  act(() => {
238
237
  tree = create(wrapComponent(item));
@@ -254,9 +253,21 @@ describe('test NotificationItem', () => {
254
253
 
255
254
  test('test render Notify not in any case', () => {
256
255
  item.content_code = 'NEW CASE';
257
- item.params = JSON.stringify({
258
- unit_id: 1,
256
+ (item.params = "{ 'unit_id': 1 }"),
257
+ act(() => {
258
+ tree = create(wrapComponent(item));
259
+ });
260
+ const instance = tree.root;
261
+ const button = instance.findByType(TouchableOpacity);
262
+ act(() => {
263
+ button.props.onPress();
259
264
  });
265
+ expect(mockNavigate).not.toHaveBeenCalledWith();
266
+ });
267
+
268
+ test('test render Notify params is object', () => {
269
+ item.content_code = 'NEW CASE';
270
+ item.params = { unit_id: 1 };
260
271
  act(() => {
261
272
  tree = create(wrapComponent(item));
262
273
  });
@@ -22,14 +22,24 @@ const NotificationItem = memo(({ item }) => {
22
22
  return /\B'|'\B/g;
23
23
  }, []);
24
24
 
25
+ const paramsJSON = useMemo(() => {
26
+ if (typeof params === 'object') {
27
+ return params;
28
+ }
29
+ // TODO return this later
30
+ let stringParams = JSON.stringify(params);
31
+ stringParams = stringParams.replace(regex, '"');
32
+ stringParams = stringParams.substring(1, stringParams.length - 1);
33
+ return JSON.parse(stringParams);
34
+ }, [params, regex]);
35
+
25
36
  let arrParams = useMemo(() => {
26
37
  const values = [];
27
- const paramsJSON = JSON.parse(params.replace(regex, '"'));
28
38
  Object.entries(paramsJSON).forEach(([key, value]) => {
29
39
  values.push(value);
30
40
  });
31
41
  return values;
32
- }, [params, regex]);
42
+ }, [paramsJSON]);
33
43
 
34
44
  const customColorText = (text, params) => {
35
45
  return text.split('**').map((str, i) =>
@@ -46,7 +56,6 @@ const NotificationItem = memo(({ item }) => {
46
56
  };
47
57
 
48
58
  const renderItem = useCallback(() => {
49
- const paramsJSON = JSON.parse(params.replace(regex, '"'));
50
59
  const booking_id = paramsJSON.booking_id && paramsJSON.booking_id;
51
60
  const unitId = paramsJSON?.unit_id;
52
61
  const sensorId = paramsJSON?.sensor_id;
@@ -513,7 +522,7 @@ const NotificationItem = memo(({ item }) => {
513
522
  iconContent: <Image source={Images.logo} style={styles.logo} />,
514
523
  };
515
524
  }
516
- }, [arrParams, content_code, navigation, params, regex, t]);
525
+ }, [arrParams, content_code, navigation, paramsJSON, t]);
517
526
 
518
527
  const { content, redirect, iconContent } = renderItem() || {};
519
528
 
@@ -73,6 +73,9 @@ describe('Test ScriptDetail', () => {
73
73
  sensor_id: 73,
74
74
  type: 'value_change',
75
75
  value: 35,
76
+ script: {
77
+ id: 1,
78
+ },
76
79
  },
77
80
  },
78
81
  };
@@ -171,7 +174,9 @@ describe('Test ScriptDetail', () => {
171
174
  await act(async () => {
172
175
  await buttonStar.props.onPress();
173
176
  });
174
- expect(axios.post).toHaveBeenCalledWith(API.AUTOMATE.STAR_SCRIPT(1));
177
+ expect(axios.post).toHaveBeenCalledWith(
178
+ API.AUTOMATE.STAR_SCRIPT(route.params.automate.id)
179
+ );
175
180
 
176
181
  axios.post.mockClear();
177
182
  axios.post.mockImplementation(async () => {
@@ -180,7 +185,9 @@ describe('Test ScriptDetail', () => {
180
185
  await act(async () => {
181
186
  await buttonStar.props.onPress();
182
187
  });
183
- expect(axios.post).toHaveBeenCalledWith(API.AUTOMATE.UNSTAR_SCRIPT(1));
188
+ expect(axios.post).toHaveBeenCalledWith(
189
+ API.AUTOMATE.UNSTAR_SCRIPT(route.params.automate.id)
190
+ );
184
191
 
185
192
  const menu = instance.findByType(MenuActionMore);
186
193
  const addFavorite = menu.props.listMenuItem[1];
@@ -191,7 +198,9 @@ describe('Test ScriptDetail', () => {
191
198
  await act(async () => {
192
199
  await menu.props.onItemClick(addFavorite);
193
200
  });
194
- expect(axios.post).toHaveBeenCalledWith(API.AUTOMATE.STAR_SCRIPT(1));
201
+ expect(axios.post).toHaveBeenCalledWith(
202
+ API.AUTOMATE.STAR_SCRIPT(route.params.automate.id)
203
+ );
195
204
 
196
205
  const removeFavorite = menu.props.listMenuItem[1];
197
206
  axios.post.mockClear();
@@ -201,7 +210,9 @@ describe('Test ScriptDetail', () => {
201
210
  await act(async () => {
202
211
  await menu.props.onItemClick(removeFavorite);
203
212
  });
204
- expect(axios.post).toHaveBeenCalledWith(API.AUTOMATE.UNSTAR_SCRIPT(1));
213
+ expect(axios.post).toHaveBeenCalledWith(
214
+ API.AUTOMATE.UNSTAR_SCRIPT(route.params.automate.id)
215
+ );
205
216
  });
206
217
 
207
218
  test('test activate one tap', async () => {
@@ -0,0 +1,32 @@
1
+ import { useCallback, useContext } from 'react';
2
+ import { SCContext, useSCContextSelector } from '../../../context';
3
+ import { Action } from '../../../context/actionType';
4
+ import { axiosPost } from '../../../utils/Apis/axios';
5
+ import { API } from '../../../configs';
6
+
7
+ export const useStarredScript = (automate) => {
8
+ const { setAction } = useContext(SCContext);
9
+ const starredScriptIds = useSCContextSelector(
10
+ (state) => state.automate.starredScriptIds
11
+ );
12
+
13
+ const isStarred = starredScriptIds.includes(automate?.script?.id);
14
+
15
+ const starScript = useCallback(async () => {
16
+ const { success } = await axiosPost(API.AUTOMATE.STAR_SCRIPT(automate?.id));
17
+ success && setAction(Action.STAR_SCRIPT, automate?.script?.id);
18
+ }, [automate, setAction]);
19
+
20
+ const unstarScript = useCallback(async () => {
21
+ const { success } = await axiosPost(
22
+ API.AUTOMATE.UNSTAR_SCRIPT(automate?.id)
23
+ );
24
+ success && setAction(Action.UNSTAR_SCRIPT, automate?.script?.id);
25
+ }, [automate, setAction]);
26
+
27
+ return {
28
+ isStarred,
29
+ starScript,
30
+ unstarScript,
31
+ };
32
+ };
@@ -19,6 +19,7 @@ import WrapHeaderScrollable from '../../commons/Sharing/WrapHeaderScrollable';
19
19
  import { API, Colors, Images } from '../../configs';
20
20
  import { usePopover } from '../../hooks/Common';
21
21
  import { useStateAlertAction } from './hooks';
22
+ import { useStarredScript } from './hooks/useStarredScript';
22
23
  import MenuActionMore from '../../commons/MenuActionMore';
23
24
  import Add from '../../../assets/images/Add.svg';
24
25
  import { useNavigation } from '@react-navigation/native';
@@ -67,7 +68,6 @@ const ScriptDetail = ({ route }) => {
67
68
  isCreateNewAction,
68
69
  isMultiUnits,
69
70
  } = params;
70
- const [isStar, setIsStar] = useState(false);
71
71
  const [scriptName, setScriptName] = useState(name);
72
72
  const [inputName, setInputName] = useState(name);
73
73
  const [
@@ -79,6 +79,8 @@ const ScriptDetail = ({ route }) => {
79
79
  ] = useStateAlertAction();
80
80
  const [data, setData] = useState([]);
81
81
 
82
+ const { isStarred, starScript, unstarScript } = useStarredScript(automate);
83
+
82
84
  const [transY] = useKeyboardAnimated(-16);
83
85
  const animatedStyle = Platform.select({
84
86
  ios: {
@@ -126,30 +128,20 @@ const ScriptDetail = ({ route }) => {
126
128
  }
127
129
  }, [stateAlertAction.isDelete, deleteScript, renameScript]);
128
130
 
129
- const starScript = useCallback(async () => {
130
- const { success } = await axiosPost(API.AUTOMATE.STAR_SCRIPT(id));
131
- success && setIsStar(true);
132
- }, [id]);
133
-
134
- const unstarScript = useCallback(async () => {
135
- const { success } = await axiosPost(API.AUTOMATE.UNSTAR_SCRIPT(id));
136
- success && setIsStar(false);
137
- }, [id]);
138
-
139
131
  const onPressStar = useCallback(() => {
140
- if (isStar) {
132
+ if (isStarred) {
141
133
  unstarScript();
142
134
  } else {
143
135
  starScript();
144
136
  }
145
- }, [isStar, starScript, unstarScript]);
137
+ }, [isStarred, starScript, unstarScript]);
146
138
 
147
139
  const listMenuItem = useMemo(
148
140
  () => [
149
141
  { text: t('rename'), doAction: onShowRename(havePermission) },
150
142
  {
151
- text: isStar ? t('remove_favorite') : t('add_favorite'),
152
- doAction: isStar ? unstarScript : starScript,
143
+ text: isStarred ? t('remove_favorite') : t('add_favorite'),
144
+ doAction: isStarred ? unstarScript : starScript,
153
145
  },
154
146
  {
155
147
  text: t('activity_log'),
@@ -170,7 +162,7 @@ const ScriptDetail = ({ route }) => {
170
162
  unit,
171
163
  onShowDelete,
172
164
  scriptName,
173
- isStar,
165
+ isStarred,
174
166
  starScript,
175
167
  unstarScript,
176
168
  ]
@@ -189,7 +181,6 @@ const ScriptDetail = ({ route }) => {
189
181
  const getScriptDetail = useCallback(async () => {
190
182
  const { success, data } = await axiosGet(API.AUTOMATE.SCRIPT(id));
191
183
  success && setData(data?.script_actions || []);
192
- success && setIsStar(data?.is_star);
193
184
  }, [id]);
194
185
 
195
186
  const onPressEdit = useCallback(() => {
@@ -318,7 +309,7 @@ const ScriptDetail = ({ route }) => {
318
309
  onPress={onPressStar}
319
310
  testID={TESTID.HEADER_DEVICE_BUTTON_STAR}
320
311
  >
321
- {isStar ? (
312
+ {isStarred ? (
322
313
  <IconFill name="star" size={25} color={Colors.Yellow6} />
323
314
  ) : (
324
315
  <IconOutline name="star" size={25} />
@@ -326,7 +317,7 @@ const ScriptDetail = ({ route }) => {
326
317
  </PreventDoubleTouch>
327
318
  );
328
319
  // eslint-disable-next-line react-hooks/exhaustive-deps
329
- }, [isStar]);
320
+ }, [isStarred]);
330
321
 
331
322
  const rightComponent = useMemo(
332
323
  () => (
@@ -342,7 +333,7 @@ const ScriptDetail = ({ route }) => {
342
333
  </View>
343
334
  ),
344
335
  // eslint-disable-next-line react-hooks/exhaustive-deps
345
- [isStar]
336
+ [isStarred]
346
337
  );
347
338
 
348
339
  useEffect(() => {
@@ -35,6 +35,11 @@ describe('Test TabHeader', () => {
35
35
  });
36
36
  const instance = tree.root;
37
37
  const touchableOpacity = instance.findAllByType(TouchableOpacity);
38
+ act(() => {
39
+ touchableOpacity[0].props.onPress();
40
+ touchableOpacity[1].props.onPress();
41
+ });
38
42
  expect(touchableOpacity).toHaveLength(3);
43
+ expect(touchableOpacity).toBeDefined();
39
44
  });
40
45
  });
@@ -21,7 +21,6 @@ import {
21
21
  import { axiosPost } from '../../utils/Apis/axios';
22
22
  import { TESTID } from '../../configs/Constants';
23
23
  import Text from '../../commons/Text';
24
- import { ToastBottomHelper } from '../../utils/Utils';
25
24
 
26
25
  const SelectUser = ({ route }) => {
27
26
  const t = useTranslations();
@@ -33,6 +32,7 @@ const SelectUser = ({ route }) => {
33
32
 
34
33
  const sharePermissions = useCallback(
35
34
  async (phone, email) => {
35
+ Keyboard.dismiss();
36
36
  let userSharedPermission = await users.filter(
37
37
  (user) => user.phone_number === phone || user.email === email
38
38
  );
@@ -45,29 +45,9 @@ const SelectUser = ({ route }) => {
45
45
  unit: unit.id,
46
46
  permissions,
47
47
  });
48
- if (success) {
49
- setUsers([...users, data.user]);
50
- } else {
51
- if (data.non_field_errors) {
52
- ToastBottomHelper.error(data.non_field_errors);
53
- } else {
54
- const textTemp = t(
55
- data?.phone
56
- ? 'text_phone_share_permission'
57
- : data?.email
58
- ? 'text_email_share_permission'
59
- : ''
60
- );
61
- ToastBottomHelper.error(
62
- t('error_share_permission', {
63
- data: phone || email,
64
- text: textTemp,
65
- })
66
- );
67
- }
68
- }
48
+ success && setUsers([...users, data.user]);
69
49
  },
70
- [users, unit.id, permissions, t]
50
+ [users, unit.id, permissions]
71
51
  );
72
52
 
73
53
  const validate = useCallback(() => {