@eohjsc/react-native-smart-city 0.3.92 → 0.3.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 (87) hide show
  1. package/package.json +5 -1
  2. package/src/commons/ActionGroup/SliderRangeTemplate.js +2 -2
  3. package/src/commons/AlertAction/index.js +5 -0
  4. package/src/commons/BottomButtonView/index.js +22 -4
  5. package/src/commons/Button/index.js +5 -0
  6. package/src/commons/Device/ConnectedViewHeader.js +0 -1
  7. package/src/commons/Device/Emergency/EmergencyDetail.js +4 -2
  8. package/src/commons/Device/Emergency/__test__/EmergencyDetail.test.js +4 -2
  9. package/src/commons/Device/ProgressBar/index.js +3 -1
  10. package/src/commons/Device/ProgressBar/styles.js +1 -4
  11. package/src/commons/Device/WindSpeed/Anemometer/index.js +1 -1
  12. package/src/commons/Header/HeaderCustom.js +8 -18
  13. package/src/commons/SubUnit/OneTap/ItemOneTap.js +84 -129
  14. package/src/commons/SubUnit/OneTap/__test__/SubUnitAutomate.test.js +74 -39
  15. package/src/commons/SubUnit/OneTap/index.js +24 -4
  16. package/src/commons/ViewButtonBottom/index.js +32 -4
  17. package/src/configs/API.js +4 -0
  18. package/src/configs/AccessibilityLabel.js +1 -0
  19. package/src/configs/BLE.js +1 -0
  20. package/src/context/actionType.ts +2 -1
  21. package/src/context/mockStore.ts +1 -0
  22. package/src/context/reducer.ts +12 -1
  23. package/src/hooks/Explore/useKeyboardAnimated.js +10 -4
  24. package/src/hooks/IoT/useBluetoothConnection.js +14 -26
  25. package/src/hooks/useMqtt.js +95 -0
  26. package/src/iot/Monitor.js +2 -1
  27. package/src/iot/RemoteControl/Bluetooth.js +56 -19
  28. package/src/iot/RemoteControl/__test__/Bluetooth.test.js +140 -0
  29. package/src/iot/mqtt.js +233 -0
  30. package/src/navigations/UnitStack.js +11 -3
  31. package/src/screens/AddLocationMaps/index.js +18 -16
  32. package/src/screens/AddLocationMaps/indexStyle.js +3 -0
  33. package/src/screens/AddNewGateway/ConnectingDevice.js +7 -0
  34. package/src/screens/AddNewGateway/RenameNewDevices.js +2 -2
  35. package/src/screens/AddNewGateway/SelectDeviceType.js +67 -9
  36. package/src/screens/AddNewGateway/__test__/ConnectingZigbeeDevice.test.js +35 -0
  37. package/src/screens/AddNewGateway/__test__/SelectDeviceType.test.js +183 -29
  38. package/src/screens/Automate/AddNewAction/NewActionWrapper.js +3 -6
  39. package/src/screens/Automate/AddNewAction/SetupConfigCondition.js +29 -29
  40. package/src/screens/Automate/AddNewAction/__test__/SetupSensor.test.js +45 -39
  41. package/src/screens/Automate/AddNewAutoSmart/AddAutomationTypeSmart.js +25 -0
  42. package/src/screens/{AddNewAutoSmart/index.js → Automate/AddNewAutoSmart/AddTypeSmart.js} +16 -51
  43. package/src/screens/Automate/AddNewAutoSmart/AddUnknownTypeSmart.js +29 -0
  44. package/src/screens/{AddNewAutoSmart → Automate/AddNewAutoSmart}/__test__/AddNewAutoSmart.test.js +11 -11
  45. package/src/screens/{AddNewAutoSmart → Automate/AddNewAutoSmart}/styles/AddNewAutoSmartStyles.js +1 -1
  46. package/src/screens/Automate/Components/InputNameStyles.js +1 -1
  47. package/src/screens/Automate/EditActionsList/__tests__/index.test.js +11 -25
  48. package/src/screens/Automate/EditActionsList/index.js +32 -33
  49. package/src/screens/Automate/MultiUnits.js +29 -19
  50. package/src/screens/Automate/ScriptDetail/__test__/index.test.js +78 -5
  51. package/src/screens/Automate/ScriptDetail/index.js +38 -10
  52. package/src/screens/Automate/Styles/MultiUnitsStyles.js +1 -1
  53. package/src/screens/Automate/__test__/MultiUnits.test.js +64 -7
  54. package/src/screens/Automate/__test__/index.test.js +45 -11
  55. package/src/screens/Automate/index.js +53 -38
  56. package/src/screens/Device/__test__/detail.test.js +1 -1
  57. package/src/screens/Device/__test__/mqttDetail.test.js +599 -0
  58. package/src/screens/Device/components/SensorDisplayItem.js +1 -7
  59. package/src/screens/Device/detail.js +64 -30
  60. package/src/screens/Device/hooks/__test__/useEvaluateValue.test.js +24 -1
  61. package/src/screens/Device/hooks/useDeviceWatchConfigControl.js +13 -3
  62. package/src/screens/SelectUnit/__test__/index.test.js +8 -13
  63. package/src/screens/Sharing/InfoMemberUnit.js +2 -2
  64. package/src/screens/Sharing/MemberList.js +28 -7
  65. package/src/screens/Sharing/__test__/InfoMemberUnit.test.js +32 -18
  66. package/src/screens/Sharing/__test__/MemberList.test.js +37 -4
  67. package/src/screens/SmartAccount/__test__/SmartAccount.test.js +8 -4
  68. package/src/screens/SmartAccount/index.js +8 -9
  69. package/src/screens/SmartAccount/style.js +8 -7
  70. package/src/screens/Unit/AddMenu.js +42 -19
  71. package/src/screens/Unit/Detail.js +4 -19
  72. package/src/screens/Unit/Summaries.js +6 -17
  73. package/src/screens/Unit/__test__/AddMenu.test.js +68 -15
  74. package/src/screens/Unit/__test__/Summaries.test.js +2 -2
  75. package/src/screens/Unit/components/AutomateScript/index.js +1 -1
  76. package/src/utils/FactoryGateway.js +525 -0
  77. package/src/utils/I18n/translations/en.js +1409 -0
  78. package/src/utils/I18n/translations/vi.js +1411 -0
  79. package/src/utils/I18n/translations.ts +2 -2
  80. package/src/utils/Permission/backend.js +7 -0
  81. package/src/utils/Route/index.js +2 -1
  82. package/src/utils/Utils.js +11 -0
  83. package/src/commons/Device/SensorConnectedStatus.js +0 -56
  84. package/src/commons/Device/__test__/SensorConnectedStatus.test.js +0 -29
  85. package/src/screens/Sharing/__test__/MemberList2.test.js +0 -74
  86. package/src/utils/I18n/translations/en.json +0 -1138
  87. package/src/utils/I18n/translations/vi.json +0 -1136
@@ -1,8 +1,10 @@
1
1
  import React from 'react';
2
2
  import { create, act } from 'react-test-renderer';
3
3
  import Toast from 'react-native-toast-message';
4
-
4
+ import { useNavigation } from '@react-navigation/native';
5
+ import { TouchableOpacity } from 'react-native';
5
6
  import MockAdapter from 'axios-mock-adapter';
7
+
6
8
  import { SCProvider } from '../../../../context';
7
9
  import { mockSCStore } from '../../../../context/mockStore';
8
10
  import ScriptDetail from '../index';
@@ -14,16 +16,16 @@ import {
14
16
  AccessibilityLabel,
15
17
  } from '../../../../configs/Constants';
16
18
  import { API } from '../../../../configs';
17
- import { TouchableOpacity } from 'react-native';
18
19
  import Routes from '../../../../utils/Route';
19
20
  import WrapHeaderScrollable from '../../../../commons/Sharing/WrapHeaderScrollable';
20
21
  import ItemAutomate from '../../../../commons/Automate/ItemAutomate';
21
22
  import api from '../../../../utils/Apis/axios';
22
23
  import Text from '../../../../commons/Text';
23
- import { useNavigation } from '@react-navigation/native';
24
+ import { ToastBottomHelper } from '../../../../utils/Utils';
25
+ import { getTranslate } from '../../../../utils/I18n';
24
26
 
25
- const wrapComponent = (route) => (
26
- <SCProvider initState={mockSCStore({})}>
27
+ const wrapComponent = (route, storeData = {}) => (
28
+ <SCProvider initState={mockSCStore(storeData)}>
27
29
  <ScriptDetail route={route} />
28
30
  </SCProvider>
29
31
  );
@@ -48,6 +50,7 @@ describe('Test ScriptDetail', () => {
48
50
  unit: 2,
49
51
  author: 'Le Minh Tam',
50
52
  id: 1,
53
+ can_edit: true,
51
54
  type: 'value_change',
52
55
  value_change: {
53
56
  condition: '<',
@@ -123,6 +126,26 @@ describe('Test ScriptDetail', () => {
123
126
  expect(Toast.show).toHaveBeenCalled();
124
127
  });
125
128
 
129
+ it('test cannot rename script', async () => {
130
+ route.params.preAutomate.can_edit = false;
131
+ const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
132
+
133
+ await act(async () => {
134
+ tree = await create(wrapComponent(route));
135
+ });
136
+ const instance = tree.root;
137
+ const menu = instance.findByProps({
138
+ accessibilityLabel: AccessibilityLabel.ICON_MORE,
139
+ });
140
+
141
+ await act(async () => {
142
+ await menu.props.onPress();
143
+ });
144
+ expect(spyToastError).toBeCalledWith(
145
+ getTranslate('en', 'only_owner_has_permission_to_edit_this_script')
146
+ );
147
+ });
148
+
126
149
  it('test delete script', async () => {
127
150
  await act(async () => {
128
151
  tree = await create(wrapComponent(route));
@@ -218,6 +241,56 @@ describe('Test ScriptDetail', () => {
218
241
  });
219
242
  });
220
243
 
244
+ it('test press add action reach limit', async () => {
245
+ mock.onGet(API.AUTOMATE.SCRIPT(1)).reply(200, data);
246
+ await act(async () => {
247
+ tree = await create(
248
+ wrapComponent(route, {
249
+ auth: {
250
+ account: {
251
+ user: {
252
+ permissions: {
253
+ max_actions_per_automation: 0,
254
+ },
255
+ },
256
+ },
257
+ },
258
+ })
259
+ );
260
+ });
261
+ const instance = tree.root;
262
+ const button = instance.find(
263
+ (el) =>
264
+ el.props.accessibilityLabel ===
265
+ AccessibilityLabel.BUTTON_ADD_SCRIPT_ACTION &&
266
+ el.type === TouchableOpacity
267
+ );
268
+ const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
269
+ await act(async () => {
270
+ await button.props.onPress();
271
+ });
272
+ expect(mockNavigate).not.toBeCalled();
273
+ expect(spyToastError).toBeCalledWith(
274
+ getTranslate('en', 'reach_max_actions_per_automation')
275
+ );
276
+ });
277
+
278
+ it('test not see add action', async () => {
279
+ route.params.preAutomate.can_edit = false;
280
+ mock.onGet(API.AUTOMATE.SCRIPT(1)).reply(200, data);
281
+ await act(async () => {
282
+ tree = await create(wrapComponent(route));
283
+ });
284
+ const instance = tree.root;
285
+ const buttons = instance.findAll(
286
+ (el) =>
287
+ el.props.accessibilityLabel ===
288
+ AccessibilityLabel.BUTTON_ADD_SCRIPT_ACTION &&
289
+ el.type === TouchableOpacity
290
+ );
291
+ expect(buttons).toHaveLength(0);
292
+ });
293
+
221
294
  it('test onPress onGoBack', async () => {
222
295
  await act(async () => {
223
296
  tree = await create(wrapComponent(route));
@@ -30,6 +30,7 @@ import { AccessibilityLabel, AUTOMATE_TYPE } from '../../../configs/Constants';
30
30
  import RenameScript from './Components/RenameScript';
31
31
  import DeleteScript from './Components/DeleteScript';
32
32
  import Images from '../../../configs/Images';
33
+ import { useBackendPermission } from '../../../utils/Permission/backend';
33
34
 
34
35
  const PreventDoubleTouch = withPreventDoubleClick(TouchableOpacity);
35
36
 
@@ -45,7 +46,9 @@ const ScriptDetail = ({ route }) => {
45
46
  saveAt,
46
47
  preAutomate = {}, // pre-loaded automate data
47
48
  newAutomate, // updated automate data
49
+ newActionsList, // updated actions list
48
50
  } = params;
51
+
49
52
  const [automate, setAutomate] = useState(preAutomate);
50
53
  const [data, setData] = useState([]);
51
54
  const [isShowRename, setIsShowRename] = useState(false);
@@ -81,10 +84,15 @@ const ScriptDetail = ({ route }) => {
81
84
  [t, onShowActivityLog]
82
85
  );
83
86
 
84
- const handleShowMenuAction = useCallback(
85
- () => showPopoverWithRef(refMenuAction),
86
- [showPopoverWithRef]
87
- );
87
+ const handleShowMenuAction = useCallback(() => {
88
+ if (!automate?.can_edit) {
89
+ ToastBottomHelper.error(
90
+ t('only_owner_has_permission_to_edit_this_script')
91
+ );
92
+ return;
93
+ }
94
+ showPopoverWithRef(refMenuAction);
95
+ }, [automate?.can_edit, showPopoverWithRef, t]);
88
96
 
89
97
  const onItemClick = useCallback((item) => {
90
98
  item.doAction();
@@ -115,14 +123,14 @@ const ScriptDetail = ({ route }) => {
115
123
  const handleScriptAction = useCallback(async () => {
116
124
  const { success } = await axiosPost(API.AUTOMATE.ACTION_ONE_TAP(id));
117
125
  if (success) {
118
- ToastBottomHelper.success(t('Activated successfully.'));
126
+ ToastBottomHelper.success(t('activated_successfully'));
119
127
  } else {
120
- ToastBottomHelper.error(t('Activation failed.'));
128
+ ToastBottomHelper.error(t('activation_failed'));
121
129
  }
122
130
  }, [id, t]);
123
131
 
124
132
  const handleUpdateAutomate = useCallback(async () => {
125
- navigate(Routes.AddNewAutoSmart, {
133
+ navigate(Routes.AddUnknownTypeSmart, {
126
134
  automate,
127
135
  closeScreen: route.name,
128
136
  });
@@ -132,6 +140,10 @@ const ScriptDetail = ({ route }) => {
132
140
  fetchAutomate();
133
141
  }, [fetchAutomate]);
134
142
 
143
+ useEffect(() => {
144
+ newActionsList && setData(newActionsList);
145
+ }, [newActionsList]);
146
+
135
147
  const rightComponent = useMemo(
136
148
  () => (
137
149
  <View style={styles.rightComponent}>
@@ -140,6 +152,7 @@ const ScriptDetail = ({ route }) => {
140
152
  onPress={handleShowMenuAction}
141
153
  ref={refMenuAction}
142
154
  style={[styles.headerButton, styles.moreButton]}
155
+ accessibilityLabel={AccessibilityLabel.ICON_MORE}
143
156
  >
144
157
  <Icon name={'more'} size={27} color={Colors.Black} />
145
158
  </TouchableOpacity>
@@ -187,7 +200,7 @@ const ScriptDetail = ({ route }) => {
187
200
  <Text type="H3" color={Colors.Gray9} semibold>
188
201
  {t('active_list')}
189
202
  </Text>
190
- {data.length > 0 && (
203
+ {data.length > 0 && !!automate?.can_edit && (
191
204
  <TouchableOpacity
192
205
  onPress={onPressEdit}
193
206
  style={styles.editButton}
@@ -204,7 +217,9 @@ const ScriptDetail = ({ route }) => {
204
217
  {data.map((item, index) => (
205
218
  <Item key={item?.id} item={item} index={index} />
206
219
  ))}
207
- <ItemAdd automate={automate} index={data.length} />
220
+ {!!automate?.can_edit && (
221
+ <ItemAdd automate={automate} index={data.length} />
222
+ )}
208
223
  </View>
209
224
  </WrapHeaderScrollable>
210
225
  <MenuActionMore
@@ -282,9 +297,14 @@ const Item = ({ item, index }) => {
282
297
  const ItemAdd = ({ automate, index }) => {
283
298
  const t = useTranslations();
284
299
  const { navigate } = useNavigation();
300
+ const permissions = useBackendPermission();
285
301
  const { name: currentScreenName } = useRoute();
286
302
 
287
303
  const onPressAddAction = useCallback(() => {
304
+ if (permissions?.max_actions_per_automation <= index) {
305
+ ToastBottomHelper.error(t('reach_max_actions_per_automation'));
306
+ return;
307
+ }
288
308
  const navParams = {
289
309
  unitId: automate.unit,
290
310
  automateId: automate.id,
@@ -295,7 +315,15 @@ const ItemAdd = ({ automate, index }) => {
295
315
  automate.unit ? Routes.SelectControlDevices : Routes.SelectUnit,
296
316
  navParams
297
317
  );
298
- }, [automate.unit, automate.id, currentScreenName, navigate]);
318
+ }, [
319
+ permissions?.max_actions_per_automation,
320
+ index,
321
+ automate.unit,
322
+ automate.id,
323
+ currentScreenName,
324
+ navigate,
325
+ t,
326
+ ]);
299
327
 
300
328
  return (
301
329
  <View style={styles.wrapItem}>
@@ -45,7 +45,7 @@ export default StyleSheet.create({
45
45
  flexDirection: 'row',
46
46
  flexWrap: 'wrap',
47
47
  },
48
- marginRigt8: {
48
+ itemOneTapMargin: {
49
49
  marginRight: 8,
50
50
  },
51
51
  addNewItem: {
@@ -15,11 +15,12 @@ import { getTranslate } from '../../../utils/I18n';
15
15
  import { AUTOMATE_TYPE } from '../../../configs/Constants';
16
16
  import api from '../../../utils/Apis/axios';
17
17
  import { API } from '../../../configs';
18
+ import { ToastBottomHelper } from '../../../utils/Utils';
18
19
 
19
20
  const mock = new MockAdapter(api.axiosInstance);
20
21
 
21
- const wrapComponent = () => (
22
- <SCProvider initState={mockSCStore({})}>
22
+ const wrapComponent = (storeData = {}) => (
23
+ <SCProvider initState={mockSCStore(storeData)}>
23
24
  <MultiUnits />
24
25
  </SCProvider>
25
26
  );
@@ -31,6 +32,7 @@ describe('Test MultiUnits', () => {
31
32
  beforeEach(() => {
32
33
  mockedNavigate.mockClear();
33
34
  useRoute.mockClear();
35
+ mock.reset();
34
36
  });
35
37
 
36
38
  it('Test is multi unit and current is Automation tab', async () => {
@@ -106,7 +108,7 @@ describe('Test MultiUnits', () => {
106
108
  ItemAddNews[0].props.onAddNew();
107
109
  });
108
110
  expect(mockedNavigate).toBeCalledWith(Routes.UnitStack, {
109
- screen: Routes.AddNewAutoSmart,
111
+ screen: Routes.AddUnknownTypeSmart,
110
112
  params: {
111
113
  automate: { unit: undefined },
112
114
  closeScreen: undefined,
@@ -151,9 +153,7 @@ describe('Test MultiUnits', () => {
151
153
  mock.onGet(API.AUTOMATE.GET_MULTI_UNITS()).reply(200, response);
152
154
  useRoute.mockReturnValue({
153
155
  params: {
154
- isMultiUnits: true,
155
- unitName: null,
156
- unit: null,
156
+ unit: {},
157
157
  },
158
158
  });
159
159
  await act(async () => {
@@ -180,9 +180,66 @@ describe('Test MultiUnits', () => {
180
180
  });
181
181
  });
182
182
 
183
+ it('create new smart but reach max', async () => {
184
+ const response = [
185
+ {
186
+ id: 1,
187
+ user: 223,
188
+ type: 'one_tap',
189
+ config: null,
190
+ value: null,
191
+ activate_at: '2021-09-22T14:16:54Z',
192
+ condition: null,
193
+ script: {
194
+ id: 1,
195
+ name: 'Tap to up down up down coc coc coc coc coc',
196
+ icon: null,
197
+ icon_kit:
198
+ 'https://eoh-gateway-backend.eoh.io/_Category_Sensors_Type_Garage_door_StyleC_olorful.png',
199
+ },
200
+ },
201
+ ];
202
+ mock.onGet(API.UNIT.AUTOMATE(1)).reply(200, response);
203
+ useRoute.mockReturnValue({
204
+ params: {
205
+ unit: { id: 1 },
206
+ },
207
+ });
208
+ await act(async () => {
209
+ tree = await create(
210
+ wrapComponent({
211
+ auth: {
212
+ account: {
213
+ user: {
214
+ permissions: {
215
+ max_automations_per_unit: 0,
216
+ },
217
+ },
218
+ },
219
+ },
220
+ })
221
+ );
222
+ });
223
+ const instance = tree.root;
224
+ const itemAddNews = instance.findAllByType(ItemAddNew);
225
+ const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
226
+ await act(async () => {
227
+ itemAddNews[0].props.onAddNew();
228
+ });
229
+ expect(mockedNavigate).not.toBeCalled();
230
+ expect(spyToastError).toBeCalledWith(
231
+ getTranslate('en', 'reach_max_automations_per_unit')
232
+ );
233
+ });
234
+
183
235
  it('Test is not multi unit', async () => {
184
236
  useRoute.mockReturnValue({
185
- params: {},
237
+ params: {
238
+ unit: {
239
+ id: 1,
240
+ name: '',
241
+ },
242
+ },
186
243
  });
187
244
  await act(async () => {
188
245
  tree = await create(wrapComponent());
@@ -13,11 +13,13 @@ import { AccessibilityLabel } from '../../../configs/Constants';
13
13
  import api from '../../../utils/Apis/axios';
14
14
  import { API } from '../../../configs';
15
15
  import { useNavigation } from '@react-navigation/native';
16
+ import { getTranslate } from '../../../utils/I18n';
17
+ import { ToastBottomHelper } from '../../../utils/Utils';
16
18
 
17
19
  const mock = new MockAdapter(api.axiosInstance);
18
20
 
19
- const wrapComponent = () => (
20
- <SCProvider initState={mockSCStore({})}>
21
+ const wrapComponent = (storeData = {}) => (
22
+ <SCProvider initState={mockSCStore(storeData)}>
21
23
  <Automate />
22
24
  </SCProvider>
23
25
  );
@@ -92,7 +94,7 @@ describe('Test Automate', () => {
92
94
  ItemAddNews[0].props.onAddNew('MultiUnit');
93
95
  });
94
96
  expect(mockedNavigate).toBeCalledWith(Routes.UnitStack, {
95
- screen: Routes.AddNewAutoSmart,
97
+ screen: Routes.AddUnknownTypeSmart,
96
98
  params: {
97
99
  closeScreen: undefined,
98
100
  automate: { unit: undefined },
@@ -100,6 +102,44 @@ describe('Test Automate', () => {
100
102
  });
101
103
  });
102
104
 
105
+ it('onPress onAddNew but reach limit', async () => {
106
+ mock.onGet(API.AUTOMATE.GET_SMART()).reply(200, [
107
+ {
108
+ type: 'MultiUnit',
109
+ unit_id: 1,
110
+ automates: [],
111
+ },
112
+ ]);
113
+
114
+ await act(async () => {
115
+ tree = await create(
116
+ wrapComponent({
117
+ auth: {
118
+ account: {
119
+ user: {
120
+ permissions: {
121
+ max_automations_per_unit: 0,
122
+ },
123
+ },
124
+ },
125
+ },
126
+ })
127
+ );
128
+ });
129
+
130
+ const instance = tree.root;
131
+
132
+ const itemAddNew = instance.findByType(ItemAddNew);
133
+ const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
134
+ await act(async () => {
135
+ itemAddNew.props.onAddNew(1, []);
136
+ });
137
+ expect(mockedNavigate).not.toBeCalled();
138
+ expect(spyToastError).toBeCalledWith(
139
+ getTranslate('en', 'reach_max_automations_per_unit')
140
+ );
141
+ });
142
+
103
143
  it('Test success get Automate onPressItem to ScriptDetail', async () => {
104
144
  const response = {
105
145
  status: 200,
@@ -193,10 +233,7 @@ describe('Test Automate', () => {
193
233
  });
194
234
 
195
235
  expect(mockedNavigate).toBeCalledWith(Routes.MultiUnits, {
196
- isMultiUnits: true,
197
- unitName: '',
198
- unit: { id: undefined },
199
- isOwner: true,
236
+ unit: { id: undefined, name: '' },
200
237
  });
201
238
 
202
239
  mockedNavigate.mockClear();
@@ -205,10 +242,7 @@ describe('Test Automate', () => {
205
242
  });
206
243
 
207
244
  expect(mockedNavigate).toBeCalledWith(Routes.MultiUnits, {
208
- isMultiUnits: false,
209
- unitName: 'La Vida',
210
- unit: { id: 3 },
211
- isOwner: false,
245
+ unit: { id: 3, name: 'La Vida' },
212
246
  });
213
247
  });
214
248
  });
@@ -25,7 +25,8 @@ import ItemAddNew from '../../commons/Device/ItemAddNew';
25
25
  import { useTranslations } from '../../hooks/Common/useTranslations';
26
26
  import { useGetIdUser } from '../../hooks/Common';
27
27
  import { AccessibilityLabel, UNIT_TYPES } from '../../configs/Constants';
28
- import { keyExtractor } from '../../utils/Utils';
28
+ import { keyExtractor, ToastBottomHelper } from '../../utils/Utils';
29
+ import { useBackendPermission } from '../../utils/Permission/backend';
29
30
 
30
31
  const Automate = () => {
31
32
  const t = useTranslations();
@@ -38,6 +39,7 @@ const Automate = () => {
38
39
  const starredScriptIds = useSCContextSelector(
39
40
  (state) => state.automate.starredScriptIds
40
41
  );
42
+ const permissions = useBackendPermission();
41
43
 
42
44
  const sortedAutomateData = useMemo(() => {
43
45
  return automatesData.map((unit) => {
@@ -64,35 +66,48 @@ const Automate = () => {
64
66
  }, 1000);
65
67
  }, []);
66
68
 
67
- const onPressItem = (item, unitId, type, isOwner) => () => {
68
- navigate(Routes.UnitStack, {
69
- screen: Routes.ScriptDetail,
70
- params: {
71
- id: item?.id,
72
- closeScreen: currentRouteName,
73
- preAutomate: item, // pre-loaded automate data
74
- },
75
- });
76
- };
69
+ const onPressItem = useCallback(
70
+ (item) => {
71
+ navigate(Routes.UnitStack, {
72
+ screen: Routes.ScriptDetail,
73
+ params: {
74
+ id: item?.id,
75
+ closeScreen: currentRouteName,
76
+ preAutomate: item, // pre-loaded automate data
77
+ },
78
+ });
79
+ },
80
+ [currentRouteName, navigate]
81
+ );
77
82
 
78
- const handleOnAddNew = (id) => () => {
79
- navigate(Routes.UnitStack, {
80
- screen: Routes.AddNewAutoSmart,
81
- params: {
82
- automate: { unit: id },
83
- closeScreen: currentRouteName,
84
- },
85
- });
86
- };
83
+ const handleOnAddNew = useCallback(
84
+ (id, automates) => {
85
+ if (id) {
86
+ // only limit per unit
87
+ if (permissions.max_automations_per_unit <= automates.length) {
88
+ ToastBottomHelper.error(t('reach_max_automations_per_unit'));
89
+ return;
90
+ }
91
+ }
92
+ navigate(Routes.UnitStack, {
93
+ screen: Routes.AddUnknownTypeSmart,
94
+ params: {
95
+ automate: { unit: id },
96
+ closeScreen: currentRouteName,
97
+ },
98
+ });
99
+ },
100
+ [currentRouteName, navigate, permissions?.max_automations_per_unit, t]
101
+ );
87
102
 
88
- const onPressArrowRight = (isMultiUnits, unitName, unitId, isOwner) => () => {
89
- navigate(Routes.MultiUnits, {
90
- isMultiUnits,
91
- unitName,
92
- unit: { id: unitId },
93
- isOwner,
94
- });
95
- };
103
+ const onPressArrowRight = useCallback(
104
+ (unit) => {
105
+ navigate(Routes.MultiUnits, {
106
+ unit,
107
+ });
108
+ },
109
+ [navigate]
110
+ );
96
111
 
97
112
  const renderItem = useCallback(
98
113
  ({ item = {} }) => {
@@ -112,7 +127,7 @@ const Automate = () => {
112
127
  isOwner={isOwner}
113
128
  automate={automate}
114
129
  wrapSyles={styles.wrapAutomateItem}
115
- onPressItem={onPressItem(automate, unit_id, type, isOwner)}
130
+ onPressItem={() => onPressItem(automate, unit_id, type, isOwner)}
116
131
  />
117
132
  );
118
133
  };
@@ -124,12 +139,12 @@ const Automate = () => {
124
139
  {type === UNIT_TYPES.MULTI ? t('multi_unit') : unit_name}
125
140
  </Text>
126
141
  <TouchableOpacity
127
- onPress={onPressArrowRight(
128
- type === UNIT_TYPES.MULTI,
129
- unit_name,
130
- unit_id,
131
- isOwner
132
- )}
142
+ onPress={() =>
143
+ onPressArrowRight({
144
+ name: unit_name,
145
+ id: unit_id,
146
+ })
147
+ }
133
148
  style={styles.arrowRightButton}
134
149
  accessibilityLabel={AccessibilityLabel.ICON_ARROW_RIGHT}
135
150
  >
@@ -143,7 +158,7 @@ const Automate = () => {
143
158
  renderItem={renderItemAutomate}
144
159
  showsHorizontalScrollIndicator={false}
145
160
  contentContainerStyle={styles.contentContainerStyle2}
146
- ListFooterComponent={renderListFooterComponent(unit_id)}
161
+ ListFooterComponent={renderListFooterComponent(unit_id, automates)}
147
162
  scrollIndicatorInsets={{ right: 1 }}
148
163
  />
149
164
  </View>
@@ -153,10 +168,10 @@ const Automate = () => {
153
168
  [sortedAutomateData]
154
169
  );
155
170
 
156
- const renderListFooterComponent = (unitId) => (
171
+ const renderListFooterComponent = (unitId, automates) => (
157
172
  <ItemAddNew
158
173
  title={t('add_new')}
159
- onAddNew={handleOnAddNew(unitId)}
174
+ onAddNew={() => handleOnAddNew(unitId, automates)}
160
175
  wrapStyle={styles.addNewItem}
161
176
  />
162
177
  );
@@ -755,7 +755,7 @@ describe('test DeviceDetail', () => {
755
755
  await create(
756
756
  wrapComponent(store, account, {
757
757
  ...route,
758
- params: { ...route.params, unitData: null, unitId },
758
+ params: { ...route.params, unitData: null, unitId: unitId },
759
759
  })
760
760
  );
761
761
  });