@eohjsc/react-native-smart-city 0.3.93 → 0.3.95

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 (48) hide show
  1. package/package.json +1 -1
  2. package/src/commons/ActionGroup/OnOffTemplate/index.js +20 -17
  3. package/src/commons/ActionGroup/__test__/OnOffButtonTemplate.test.js +1 -1
  4. package/src/commons/ActionGroup/__test__/OnOffTemplate.test.js +5 -24
  5. package/src/commons/SubUnit/OneTap/ItemOneTap.js +83 -85
  6. package/src/commons/SubUnit/OneTap/__test__/SubUnitAutomate.test.js +68 -24
  7. package/src/commons/SubUnit/OneTap/index.js +21 -1
  8. package/src/configs/API.js +1 -0
  9. package/src/screens/ActivityLog/__test__/index.test.js +60 -2
  10. package/src/screens/ActivityLog/hooks/__test__/index.test.js +5 -0
  11. package/src/screens/ActivityLog/hooks/index.js +11 -0
  12. package/src/screens/AddLocationMaps/index.js +1 -1
  13. package/src/screens/AddNewGateway/ConnectingDevice.js +7 -0
  14. package/src/screens/AddNewGateway/ConnectingWifiDevice.js +6 -4
  15. package/src/screens/AddNewGateway/ConnectingWifiGuide.js +0 -1
  16. package/src/screens/AddNewGateway/RenameNewDevices.js +2 -2
  17. package/src/screens/AddNewGateway/ScanWifiDeviceQR.js +1 -6
  18. package/src/screens/AddNewGateway/SelectDeviceType.js +67 -9
  19. package/src/screens/AddNewGateway/ShareWifiPassword.js +20 -3
  20. package/src/screens/AddNewGateway/__test__/ConnectingZigbeeDevice.test.js +35 -0
  21. package/src/screens/AddNewGateway/__test__/ScanWifiDeviceQR.test.js +0 -10
  22. package/src/screens/AddNewGateway/__test__/SelectDeviceType.test.js +183 -29
  23. package/src/screens/AllGateway/GatewayInfo/index.js +3 -3
  24. package/src/screens/Automate/AddNewAction/SetupConfigCondition.js +0 -1
  25. package/src/screens/Automate/MultiUnits.js +32 -18
  26. package/src/screens/Automate/ScriptDetail/__test__/index.test.js +36 -2
  27. package/src/screens/Automate/ScriptDetail/index.js +17 -3
  28. package/src/screens/Automate/Styles/MultiUnitsStyles.js +1 -1
  29. package/src/screens/Automate/__test__/MultiUnits.test.js +125 -8
  30. package/src/screens/Automate/__test__/index.test.js +95 -11
  31. package/src/screens/Automate/index.js +62 -38
  32. package/src/screens/Device/hooks/__test__/useEvaluateValue.test.js +24 -1
  33. package/src/screens/Sharing/InfoMemberUnit.js +2 -2
  34. package/src/screens/Sharing/MemberList.js +28 -7
  35. package/src/screens/Sharing/__test__/InfoMemberUnit.test.js +32 -18
  36. package/src/screens/Sharing/__test__/MemberList.test.js +37 -4
  37. package/src/screens/SubUnit/AddSubUnit.js +1 -1
  38. package/src/screens/SubUnit/__test__/AddSubUnit.test.js +16 -3
  39. package/src/screens/Unit/AddMenu.js +42 -19
  40. package/src/screens/Unit/__test__/AddMenu.test.js +68 -15
  41. package/src/screens/Unit/components/AutomateScript/index.js +1 -1
  42. package/src/utils/I18n/translations/en.js +1409 -0
  43. package/src/utils/I18n/translations/vi.js +1414 -0
  44. package/src/utils/I18n/translations.ts +2 -2
  45. package/src/utils/Permission/backend.js +7 -0
  46. package/src/screens/Sharing/__test__/MemberList2.test.js +0 -74
  47. package/src/utils/I18n/translations/en.json +0 -1142
  48. package/src/utils/I18n/translations/vi.json +0 -1139
@@ -17,20 +17,17 @@ import { AUTOMATE_TABS, AUTOMATE_TYPE } from '../../configs/Constants';
17
17
  import ItemOneTap from '../../commons/SubUnit/OneTap/ItemOneTap';
18
18
  import Routes from '../../utils/Route';
19
19
  import ItemAddNew from '../../commons/Device/ItemAddNew';
20
+ import { ToastBottomHelper } from '../../utils/Utils';
21
+ import { useBackendPermission } from '../../utils/Permission/backend';
20
22
 
21
23
  const MultiUnits = () => {
22
24
  const t = useTranslations();
23
25
  const isFocused = useIsFocused();
24
26
  const { navigate } = useNavigation();
25
27
  const { params = {}, name: currentRouteName } = useRoute();
26
- const {
27
- isMultiUnits = false,
28
- unitName = '',
29
- unit,
30
- isOwner,
31
- newAutomate,
32
- } = params;
28
+ const { unit, newAutomate } = params;
33
29
  const [data, setData] = useState([]);
30
+ const permissions = useBackendPermission();
34
31
 
35
32
  const [tabActive, setTabActive] = useState(AUTOMATE_TABS.SCENARIO);
36
33
 
@@ -40,20 +37,20 @@ const MultiUnits = () => {
40
37
 
41
38
  const getData = useCallback(
42
39
  async (fetchParams) => {
43
- if (isMultiUnits) {
40
+ if (unit?.id) {
41
+ await fetchWithCache(API.UNIT.AUTOMATE(unit?.id), {}, (response) => {
42
+ const { success, data: automateData } = response;
43
+ success && setData(automateData);
44
+ });
45
+ } else {
44
46
  const { success, data: automateData } = await axiosGet(
45
47
  API.AUTOMATE.GET_MULTI_UNITS(),
46
48
  fetchParams
47
49
  );
48
50
  success && setData(automateData);
49
- } else {
50
- await fetchWithCache(API.UNIT.AUTOMATE(unit?.id), {}, (response) => {
51
- const { success, data: automateData } = response;
52
- success && setData(automateData);
53
- });
54
51
  }
55
52
  },
56
- [isMultiUnits, unit?.id]
53
+ [unit?.id]
57
54
  );
58
55
 
59
56
  const onPressTabName = (tab) => () => {
@@ -79,6 +76,15 @@ const MultiUnits = () => {
79
76
  }, [newAutomate, onPressItem]);
80
77
 
81
78
  const handleOnAddNew = useCallback(() => {
79
+ if (unit?.id) {
80
+ if (permissions?.max_automations_per_unit <= data.length) {
81
+ ToastBottomHelper.error(t('reach_max_automations_per_unit'));
82
+ return;
83
+ }
84
+ } else if (!permissions?.smart_script_for_multi_unit) {
85
+ ToastBottomHelper.error(t('no_permission_smart_script_for_multi_unit'));
86
+ return;
87
+ }
82
88
  if (tabActive === AUTOMATE_TABS.SCENARIO) {
83
89
  navigate(Routes.UnitStack, {
84
90
  screen: Routes.ScenarioName,
@@ -97,7 +103,16 @@ const MultiUnits = () => {
97
103
  closeScreen: currentRouteName,
98
104
  },
99
105
  });
100
- }, [currentRouteName, navigate, tabActive, unit?.id]);
106
+ }, [
107
+ currentRouteName,
108
+ data.length,
109
+ navigate,
110
+ permissions?.max_automations_per_unit,
111
+ permissions?.smart_script_for_multi_unit,
112
+ t,
113
+ tabActive,
114
+ unit?.id,
115
+ ]);
101
116
 
102
117
  const renderContent = useMemo(() => {
103
118
  const listItems = data.filter((item) =>
@@ -116,11 +131,10 @@ const MultiUnits = () => {
116
131
  {sortedListItems.map((item, index) => (
117
132
  <ItemOneTap
118
133
  key={(item?.id || index).toString()}
119
- isOwner={isOwner}
120
134
  automate={item}
121
135
  wrapSyles={[
122
136
  styles.wrapAutomateItem,
123
- index % 2 === 0 && styles.marginRigt8,
137
+ index % 2 === 0 && styles.itemOneTapMargin,
124
138
  ]}
125
139
  onPressItem={() => onPressItem(item)}
126
140
  />
@@ -142,7 +156,7 @@ const MultiUnits = () => {
142
156
  return (
143
157
  <View style={styles.wrap}>
144
158
  <WrapHeaderScrollable
145
- title={isMultiUnits ? t('multi_units_automate') : unitName}
159
+ title={unit?.id ? unit?.name : t('multi_units_automate')}
146
160
  headerAniStyle={styles.headerAniStyle}
147
161
  >
148
162
  <View style={styles.wrapContent}>
@@ -24,8 +24,8 @@ import Text from '../../../../commons/Text';
24
24
  import { ToastBottomHelper } from '../../../../utils/Utils';
25
25
  import { getTranslate } from '../../../../utils/I18n';
26
26
 
27
- const wrapComponent = (route) => (
28
- <SCProvider initState={mockSCStore({})}>
27
+ const wrapComponent = (route, storeData = {}) => (
28
+ <SCProvider initState={mockSCStore(storeData)}>
29
29
  <ScriptDetail route={route} />
30
30
  </SCProvider>
31
31
  );
@@ -241,6 +241,40 @@ describe('Test ScriptDetail', () => {
241
241
  });
242
242
  });
243
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
+
244
278
  it('test not see add action', async () => {
245
279
  route.params.preAutomate.can_edit = false;
246
280
  mock.onGet(API.AUTOMATE.SCRIPT(1)).reply(200, data);
@@ -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
 
@@ -122,9 +123,9 @@ const ScriptDetail = ({ route }) => {
122
123
  const handleScriptAction = useCallback(async () => {
123
124
  const { success } = await axiosPost(API.AUTOMATE.ACTION_ONE_TAP(id));
124
125
  if (success) {
125
- ToastBottomHelper.success(t('Activated successfully.'));
126
+ ToastBottomHelper.success(t('activated_successfully'));
126
127
  } else {
127
- ToastBottomHelper.error(t('Activation failed.'));
128
+ ToastBottomHelper.error(t('activation_failed'));
128
129
  }
129
130
  }, [id, t]);
130
131
 
@@ -296,9 +297,14 @@ const Item = ({ item, index }) => {
296
297
  const ItemAdd = ({ automate, index }) => {
297
298
  const t = useTranslations();
298
299
  const { navigate } = useNavigation();
300
+ const permissions = useBackendPermission();
299
301
  const { name: currentScreenName } = useRoute();
300
302
 
301
303
  const onPressAddAction = useCallback(() => {
304
+ if (permissions?.max_actions_per_automation <= index) {
305
+ ToastBottomHelper.error(t('reach_max_actions_per_automation'));
306
+ return;
307
+ }
302
308
  const navParams = {
303
309
  unitId: automate.unit,
304
310
  automateId: automate.id,
@@ -309,7 +315,15 @@ const ItemAdd = ({ automate, index }) => {
309
315
  automate.unit ? Routes.SelectControlDevices : Routes.SelectUnit,
310
316
  navParams
311
317
  );
312
- }, [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
+ ]);
313
327
 
314
328
  return (
315
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 () => {
@@ -78,7 +80,19 @@ describe('Test MultiUnits', () => {
78
80
  },
79
81
  });
80
82
  await act(async () => {
81
- tree = await create(wrapComponent());
83
+ tree = await create(
84
+ wrapComponent({
85
+ auth: {
86
+ account: {
87
+ user: {
88
+ permissions: {
89
+ smart_script_for_multi_unit: true,
90
+ },
91
+ },
92
+ },
93
+ },
94
+ })
95
+ );
82
96
  });
83
97
  const instance = tree.root;
84
98
  const WrapHeaderScrollables = instance.findAllByType(WrapHeaderScrollable);
@@ -151,13 +165,23 @@ describe('Test MultiUnits', () => {
151
165
  mock.onGet(API.AUTOMATE.GET_MULTI_UNITS()).reply(200, response);
152
166
  useRoute.mockReturnValue({
153
167
  params: {
154
- isMultiUnits: true,
155
- unitName: null,
156
- unit: null,
168
+ unit: {},
157
169
  },
158
170
  });
159
171
  await act(async () => {
160
- tree = await create(wrapComponent());
172
+ tree = await create(
173
+ wrapComponent({
174
+ auth: {
175
+ account: {
176
+ user: {
177
+ permissions: {
178
+ smart_script_for_multi_unit: true,
179
+ },
180
+ },
181
+ },
182
+ },
183
+ })
184
+ );
161
185
  });
162
186
  const instance = tree.root;
163
187
  const WrapHeaderScrollables = instance.findAllByType(WrapHeaderScrollable);
@@ -180,9 +204,102 @@ describe('Test MultiUnits', () => {
180
204
  });
181
205
  });
182
206
 
207
+ it('create new smart but reach max', async () => {
208
+ const response = [
209
+ {
210
+ id: 1,
211
+ user: 223,
212
+ type: 'one_tap',
213
+ config: null,
214
+ value: null,
215
+ activate_at: '2021-09-22T14:16:54Z',
216
+ condition: null,
217
+ script: {
218
+ id: 1,
219
+ name: 'Tap to up down up down coc coc coc coc coc',
220
+ icon: null,
221
+ icon_kit:
222
+ 'https://eoh-gateway-backend.eoh.io/_Category_Sensors_Type_Garage_door_StyleC_olorful.png',
223
+ },
224
+ },
225
+ ];
226
+ mock.onGet(API.UNIT.AUTOMATE(1)).reply(200, response);
227
+ useRoute.mockReturnValue({
228
+ params: {
229
+ unit: { id: 1 },
230
+ },
231
+ });
232
+ await act(async () => {
233
+ tree = await create(
234
+ wrapComponent({
235
+ auth: {
236
+ account: {
237
+ user: {
238
+ permissions: {
239
+ max_automations_per_unit: 0,
240
+ },
241
+ },
242
+ },
243
+ },
244
+ })
245
+ );
246
+ });
247
+ const instance = tree.root;
248
+ const itemAddNews = instance.findAllByType(ItemAddNew);
249
+ const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
250
+ await act(async () => {
251
+ itemAddNews[0].props.onAddNew();
252
+ });
253
+ expect(mockedNavigate).not.toBeCalled();
254
+ expect(spyToastError).toBeCalledWith(
255
+ getTranslate('en', 'reach_max_automations_per_unit')
256
+ );
257
+ });
258
+
259
+ it('create new smart multi unit but missing permission', async () => {
260
+ mock.onGet(API.UNIT.AUTOMATE(1)).reply(200, []);
261
+ useRoute.mockReturnValue({
262
+ params: {
263
+ isMultiUnits: true,
264
+ unitName: null,
265
+ unit: null,
266
+ },
267
+ });
268
+ await act(async () => {
269
+ tree = await create(
270
+ wrapComponent({
271
+ auth: {
272
+ account: {
273
+ user: {
274
+ permissions: {
275
+ smart_script_for_multi_unit: false,
276
+ },
277
+ },
278
+ },
279
+ },
280
+ })
281
+ );
282
+ });
283
+ const instance = tree.root;
284
+ const itemAddNews = instance.findAllByType(ItemAddNew);
285
+ const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
286
+ await act(async () => {
287
+ itemAddNews[0].props.onAddNew();
288
+ });
289
+ expect(mockedNavigate).not.toBeCalled();
290
+ expect(spyToastError).toBeCalledWith(
291
+ getTranslate('en', 'no_permission_smart_script_for_multi_unit')
292
+ );
293
+ });
294
+
183
295
  it('Test is not multi unit', async () => {
184
296
  useRoute.mockReturnValue({
185
- params: {},
297
+ params: {
298
+ unit: {
299
+ id: 1,
300
+ name: '',
301
+ },
302
+ },
186
303
  });
187
304
  await act(async () => {
188
305
  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
  );
@@ -79,7 +81,19 @@ describe('Test Automate', () => {
79
81
  mock.onGet(API.AUTOMATE.GET_SMART()).reply(200, response.data);
80
82
 
81
83
  await act(async () => {
82
- tree = await create(wrapComponent());
84
+ tree = await create(
85
+ wrapComponent({
86
+ auth: {
87
+ account: {
88
+ user: {
89
+ permissions: {
90
+ smart_script_for_multi_unit: true,
91
+ },
92
+ },
93
+ },
94
+ },
95
+ })
96
+ );
83
97
  });
84
98
 
85
99
  const instance = tree.root;
@@ -100,6 +114,44 @@ describe('Test Automate', () => {
100
114
  });
101
115
  });
102
116
 
117
+ it('onPress onAddNew but reach limit', async () => {
118
+ mock.onGet(API.AUTOMATE.GET_SMART()).reply(200, [
119
+ {
120
+ type: 'MultiUnit',
121
+ unit_id: 1,
122
+ automates: [],
123
+ },
124
+ ]);
125
+
126
+ await act(async () => {
127
+ tree = await create(
128
+ wrapComponent({
129
+ auth: {
130
+ account: {
131
+ user: {
132
+ permissions: {
133
+ max_automations_per_unit: 0,
134
+ },
135
+ },
136
+ },
137
+ },
138
+ })
139
+ );
140
+ });
141
+
142
+ const instance = tree.root;
143
+
144
+ const itemAddNew = instance.findByType(ItemAddNew);
145
+ const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
146
+ await act(async () => {
147
+ itemAddNew.props.onAddNew(1, []);
148
+ });
149
+ expect(mockedNavigate).not.toBeCalled();
150
+ expect(spyToastError).toBeCalledWith(
151
+ getTranslate('en', 'reach_max_automations_per_unit')
152
+ );
153
+ });
154
+
103
155
  it('Test success get Automate onPressItem to ScriptDetail', async () => {
104
156
  const response = {
105
157
  status: 200,
@@ -193,10 +245,7 @@ describe('Test Automate', () => {
193
245
  });
194
246
 
195
247
  expect(mockedNavigate).toBeCalledWith(Routes.MultiUnits, {
196
- isMultiUnits: true,
197
- unitName: '',
198
- unit: { id: undefined },
199
- isOwner: true,
248
+ unit: { id: undefined, name: '' },
200
249
  });
201
250
 
202
251
  mockedNavigate.mockClear();
@@ -205,10 +254,45 @@ describe('Test Automate', () => {
205
254
  });
206
255
 
207
256
  expect(mockedNavigate).toBeCalledWith(Routes.MultiUnits, {
208
- isMultiUnits: false,
209
- unitName: 'La Vida',
210
- unit: { id: 3 },
211
- isOwner: false,
257
+ unit: { id: 3, name: 'La Vida' },
212
258
  });
213
259
  });
260
+
261
+ it('onPress onAddNew multi unit script but missing permission', async () => {
262
+ mock.onGet(API.AUTOMATE.GET_SMART()).reply(200, [
263
+ {
264
+ type: 'MultiUnit',
265
+ unit_id: null,
266
+ automates: [],
267
+ },
268
+ ]);
269
+
270
+ await act(async () => {
271
+ tree = await create(
272
+ wrapComponent({
273
+ auth: {
274
+ account: {
275
+ user: {
276
+ permissions: {
277
+ smart_script_for_multi_unit: false,
278
+ },
279
+ },
280
+ },
281
+ },
282
+ })
283
+ );
284
+ });
285
+
286
+ const instance = tree.root;
287
+
288
+ const itemAddNew = instance.findByType(ItemAddNew);
289
+ const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
290
+ await act(async () => {
291
+ itemAddNew.props.onAddNew(1, []);
292
+ });
293
+ expect(mockedNavigate).not.toBeCalled();
294
+ expect(spyToastError).toBeCalledWith(
295
+ getTranslate('en', 'no_permission_smart_script_for_multi_unit')
296
+ );
297
+ });
214
298
  });
@@ -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,57 @@ 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.AddUnknownTypeSmart,
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
+ } else if (!permissions?.smart_script_for_multi_unit) {
92
+ ToastBottomHelper.error(t('no_permission_smart_script_for_multi_unit'));
93
+ return;
94
+ }
95
+ navigate(Routes.UnitStack, {
96
+ screen: Routes.AddUnknownTypeSmart,
97
+ params: {
98
+ automate: { unit: id },
99
+ closeScreen: currentRouteName,
100
+ },
101
+ });
102
+ },
103
+ [
104
+ currentRouteName,
105
+ navigate,
106
+ permissions?.max_automations_per_unit,
107
+ permissions?.smart_script_for_multi_unit,
108
+ t,
109
+ ]
110
+ );
87
111
 
88
- const onPressArrowRight = (isMultiUnits, unitName, unitId, isOwner) => () => {
89
- navigate(Routes.MultiUnits, {
90
- isMultiUnits,
91
- unitName,
92
- unit: { id: unitId },
93
- isOwner,
94
- });
95
- };
112
+ const onPressArrowRight = useCallback(
113
+ (unit) => {
114
+ navigate(Routes.MultiUnits, {
115
+ unit,
116
+ });
117
+ },
118
+ [navigate]
119
+ );
96
120
 
97
121
  const renderItem = useCallback(
98
122
  ({ item = {} }) => {
@@ -112,7 +136,7 @@ const Automate = () => {
112
136
  isOwner={isOwner}
113
137
  automate={automate}
114
138
  wrapSyles={styles.wrapAutomateItem}
115
- onPressItem={onPressItem(automate, unit_id, type, isOwner)}
139
+ onPressItem={() => onPressItem(automate, unit_id, type, isOwner)}
116
140
  />
117
141
  );
118
142
  };
@@ -124,12 +148,12 @@ const Automate = () => {
124
148
  {type === UNIT_TYPES.MULTI ? t('multi_unit') : unit_name}
125
149
  </Text>
126
150
  <TouchableOpacity
127
- onPress={onPressArrowRight(
128
- type === UNIT_TYPES.MULTI,
129
- unit_name,
130
- unit_id,
131
- isOwner
132
- )}
151
+ onPress={() =>
152
+ onPressArrowRight({
153
+ name: unit_name,
154
+ id: unit_id,
155
+ })
156
+ }
133
157
  style={styles.arrowRightButton}
134
158
  accessibilityLabel={AccessibilityLabel.ICON_ARROW_RIGHT}
135
159
  >
@@ -143,7 +167,7 @@ const Automate = () => {
143
167
  renderItem={renderItemAutomate}
144
168
  showsHorizontalScrollIndicator={false}
145
169
  contentContainerStyle={styles.contentContainerStyle2}
146
- ListFooterComponent={renderListFooterComponent(unit_id)}
170
+ ListFooterComponent={renderListFooterComponent(unit_id, automates)}
147
171
  scrollIndicatorInsets={{ right: 1 }}
148
172
  />
149
173
  </View>
@@ -153,10 +177,10 @@ const Automate = () => {
153
177
  [sortedAutomateData]
154
178
  );
155
179
 
156
- const renderListFooterComponent = (unitId) => (
180
+ const renderListFooterComponent = (unitId, automates) => (
157
181
  <ItemAddNew
158
182
  title={t('add_new')}
159
- onAddNew={handleOnAddNew(unitId)}
183
+ onAddNew={() => handleOnAddNew(unitId, automates)}
160
184
  wrapStyle={styles.addNewItem}
161
185
  />
162
186
  );