@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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eohjsc/react-native-smart-city",
3
3
  "title": "React Native Smart Home",
4
- "version": "0.3.93",
4
+ "version": "0.3.95",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -22,7 +22,7 @@ const getComponent = (template) => {
22
22
  }
23
23
  };
24
24
 
25
- const OnOffTemplate = memo(({ actionGroup = {}, doAction, sensor }) => {
25
+ const OnOffTemplate = memo(({ actionGroup = {}, doAction, sensor = {} }) => {
26
26
  const { configuration = {} } = actionGroup;
27
27
  const {
28
28
  action_on_data,
@@ -41,6 +41,7 @@ const OnOffTemplate = memo(({ actionGroup = {}, doAction, sensor }) => {
41
41
 
42
42
  // eslint-disable-next-line no-unused-vars
43
43
  const [configValues, _] = useConfigGlobalState('configValues');
44
+ const { device_type, is_managed_by_backend } = sensor;
44
45
 
45
46
  const getIsOnValue = useCallback(() => {
46
47
  const configValue = configValues[config];
@@ -65,34 +66,36 @@ const OnOffTemplate = memo(({ actionGroup = {}, doAction, sensor }) => {
65
66
  return;
66
67
  }
67
68
  let data;
68
- if (sensor?.device_type === DEVICE_TYPE.GOOGLE_HOME) {
69
- data = null;
70
- } else {
69
+ if (
70
+ allow_config_store_value &&
71
+ config &&
72
+ device_type !== DEVICE_TYPE.GOOGLE_HOME
73
+ ) {
71
74
  data = {
72
- value: isOn ? 0 : 1,
73
- state: isOn ? 0 : 1, // todo Bang remove this for Zigbee
75
+ config_id: config,
76
+ config_value: isOn ? 0 : 1,
74
77
  };
75
- if (allow_config_store_value && config) {
76
- data.config_id = config;
77
- data.config_value = data.value;
78
- }
79
78
  }
80
- if (sensor?.device_type === DEVICE_TYPE.LG_THINQ) {
79
+
80
+ if (device_type === DEVICE_TYPE.LG_THINQ) {
81
81
  setTempIsOn((prev) => !prev);
82
82
  }
83
83
  await doAction(action_data, data);
84
84
  updateStatusFromPusher(); // todo Bang read about this magic
85
85
 
86
- sensor?.is_managed_by_backend &&
86
+ if (
87
+ is_managed_by_backend &&
87
88
  config &&
88
- sensor.device_type === DEVICE_TYPE.LG_THINQ &&
89
+ device_type === DEVICE_TYPE.LG_THINQ
90
+ ) {
89
91
  watchMultiConfigs([config]);
92
+ }
90
93
  }, [
91
94
  isOn,
92
95
  action_off_data,
93
96
  action_on_data,
94
- sensor?.device_type,
95
- sensor?.is_managed_by_backend,
97
+ device_type,
98
+ is_managed_by_backend,
96
99
  allow_config_store_value,
97
100
  config,
98
101
  doAction,
@@ -110,10 +113,10 @@ const OnOffTemplate = memo(({ actionGroup = {}, doAction, sensor }) => {
110
113
  }, [isOn]);
111
114
 
112
115
  useEffect(() => {
113
- if (sensor?.device_type !== DEVICE_TYPE.LG_THINQ) {
116
+ if (device_type !== DEVICE_TYPE.LG_THINQ) {
114
117
  setTempIsOn(getIsOnValue());
115
118
  }
116
- }, [getIsOnValue, sensor?.device_type]);
119
+ }, [getIsOnValue, device_type]);
117
120
 
118
121
  const Component = useMemo(() => {
119
122
  return getComponent(actionGroup.template);
@@ -122,7 +122,7 @@ describe('Test OneBigButtonTemplate', () => {
122
122
  await act(async () => {
123
123
  await button.props.onPress();
124
124
  });
125
- expect(mockDoAction).toHaveBeenCalledWith(action_data, data2);
125
+ expect(mockDoAction).toHaveBeenCalledWith(action_data, undefined);
126
126
  };
127
127
 
128
128
  it('action state on', async () => {
@@ -163,10 +163,7 @@ describe('Test OnOffTemplate', () => {
163
163
  await act(async () => {
164
164
  await template.props.triggerAction();
165
165
  });
166
- expect(mockDoAction).toHaveBeenCalledWith(action_off_data, {
167
- value: 0,
168
- state: 0,
169
- });
166
+ expect(mockDoAction).toHaveBeenCalledWith(action_off_data, undefined);
170
167
  expect(watchMultiConfigs).toBeCalledTimes(0);
171
168
  });
172
169
 
@@ -183,10 +180,7 @@ describe('Test OnOffTemplate', () => {
183
180
  await act(async () => {
184
181
  await template.props.triggerAction();
185
182
  });
186
- expect(mockDoAction).toHaveBeenCalledWith(action_on_data, {
187
- value: 1,
188
- state: 1,
189
- });
183
+ expect(mockDoAction).toHaveBeenCalledWith(action_on_data, undefined);
190
184
  });
191
185
 
192
186
  it('template OnOffSimpleActionTemplate doAction with is_on_value and is_managed_by_backend', async () => {
@@ -201,10 +195,7 @@ describe('Test OnOffTemplate', () => {
201
195
  await act(async () => {
202
196
  await template.props.triggerAction();
203
197
  });
204
- expect(mockDoAction).toHaveBeenCalledWith(action_off_data, {
205
- value: 0,
206
- state: 0,
207
- });
198
+ expect(mockDoAction).toHaveBeenCalledWith(action_off_data, undefined);
208
199
  expect(watchMultiConfigs).toBeCalledTimes(0);
209
200
  });
210
201
 
@@ -231,10 +222,7 @@ describe('Test OnOffTemplate', () => {
231
222
  await act(async () => {
232
223
  await template[0].props.triggerAction();
233
224
  });
234
- expect(mockDoAction).toHaveBeenCalledWith(action_data, {
235
- state: 0,
236
- value: 0,
237
- });
225
+ expect(mockDoAction).toHaveBeenCalledWith(action_data, undefined);
238
226
  });
239
227
 
240
228
  it('render with template OnOffSimpleActionTemplate with just action_data lg_thinq', async () => {
@@ -269,10 +257,7 @@ describe('Test OnOffTemplate', () => {
269
257
  await act(async () => {
270
258
  jest.runAllTimers();
271
259
  });
272
- expect(mockDoAction).toHaveBeenCalledWith(action_data, {
273
- value: 0,
274
- state: 0,
275
- });
260
+ expect(mockDoAction).toHaveBeenCalledWith(action_data, undefined);
276
261
  });
277
262
 
278
263
  it('render with template OnOffSimpleActionTemplate with action_data zigbee trigger off', async () => {
@@ -303,10 +288,8 @@ describe('Test OnOffTemplate', () => {
303
288
  await template[0].props.triggerAction();
304
289
  });
305
290
  expect(mockDoAction).toHaveBeenCalledWith(action_off_data, {
306
- state: 0,
307
291
  config_id: 5,
308
292
  config_value: 0,
309
- value: 0,
310
293
  });
311
294
  });
312
295
 
@@ -338,10 +321,8 @@ describe('Test OnOffTemplate', () => {
338
321
  await template[0].props.triggerAction();
339
322
  });
340
323
  expect(mockDoAction).toHaveBeenCalledWith(action_on_data, {
341
- state: 1, // todo Bang remove this after production
342
324
  config_id: 5,
343
325
  config_value: 1,
344
- value: 1, // this is correct
345
326
  });
346
327
  });
347
328
 
@@ -19,99 +19,97 @@ import { useNavigation } from '@react-navigation/native';
19
19
  import Routes from '../../../utils/Route';
20
20
  import { AccessibilityLabel, AUTOMATE_TYPE } from '../../../configs/Constants';
21
21
 
22
- const ItemOneTap = memo(
23
- ({ isOwner, automate = {}, unit, wrapSyles, onPressItem }) => {
24
- const { navigate } = useNavigation();
25
- const { id, type, script, activate_at, author = '' } = automate;
26
- const t = useTranslations();
22
+ const ItemOneTap = memo(({ automate = {}, wrapSyles, onPressItem }) => {
23
+ const { navigate } = useNavigation();
24
+ const { id, type, script, activate_at, author = '' } = automate;
25
+ const t = useTranslations();
27
26
 
28
- const goToDetail = useCallback(() => {
29
- navigate(Routes.ScriptDetail, {
30
- id,
31
- preAutomate: automate,
32
- });
33
- }, [automate, navigate, id]);
27
+ const goToDetail = useCallback(() => {
28
+ navigate(Routes.ScriptDetail, {
29
+ id,
30
+ preAutomate: automate,
31
+ });
32
+ }, [automate, navigate, id]);
34
33
 
35
- const handleScriptAction = useCallback(async () => {
36
- const { success } = await axiosPost(API.AUTOMATE.ACTION_ONE_TAP(id));
37
- if (success) {
38
- ToastBottomHelper.success(t('Activated successfully.'));
39
- } else {
40
- ToastBottomHelper.error(t('Activation failed.'));
41
- }
42
- // eslint-disable-next-line react-hooks/exhaustive-deps
43
- }, [id]);
34
+ const handleScriptAction = useCallback(async () => {
35
+ const { success } = await axiosPost(API.AUTOMATE.ACTION_ONE_TAP(id));
36
+ if (success) {
37
+ ToastBottomHelper.success(t('activated_successfully'));
38
+ } else {
39
+ ToastBottomHelper.error(t('activation_failed'));
40
+ }
41
+ // eslint-disable-next-line react-hooks/exhaustive-deps
42
+ }, [id]);
44
43
 
45
- const displayIcon = () => {
46
- const iconKit = script?.icon_kit;
47
- if (iconKit) {
48
- return <FImage source={{ uri: iconKit }} style={styles.iconSensor} />;
49
- }
50
- if (type === AUTOMATE_TYPE.ONE_TAP) {
51
- return <OneTap />;
52
- }
53
- if (type === AUTOMATE_TYPE.VALUE_CHANGE) {
54
- return <ValueChange />;
55
- }
56
- if ([AUTOMATE_TYPE.EVENT].includes(type)) {
57
- return <Event />;
58
- }
59
- return <Schedule />;
60
- };
61
- const activateAt = activate_at
62
- ? timeDifference(new Date(), moment(activate_at), true)
63
- : null;
64
- return (
65
- <TouchableWithoutFeedback
66
- onPress={onPressItem || goToDetail}
67
- accessibilityLabel={`${AccessibilityLabel.AUTOMATE_SCRIPT_NAME}-${id}`}
68
- >
69
- <View style={[styles.container, wrapSyles]}>
70
- <View style={styles.boxIcon}>
71
- {displayIcon()}
72
- {type === AUTOMATE_TYPE.ONE_TAP && (
73
- <TouchableOpacity
74
- accessibilityLabel={AccessibilityLabel.AUTOMATE_SCRIPT_ACTION}
75
- onPress={handleScriptAction}
76
- >
77
- <CheckCircle />
78
- </TouchableOpacity>
79
- )}
80
- </View>
81
- <TouchableOpacity
82
- accessibilityLabel={AccessibilityLabel.GO_DETAIL}
83
- onPress={onPressItem || goToDetail}
44
+ const displayIcon = () => {
45
+ const iconKit = script?.icon_kit;
46
+ if (iconKit) {
47
+ return <FImage source={{ uri: iconKit }} style={styles.iconSensor} />;
48
+ }
49
+ if (type === AUTOMATE_TYPE.ONE_TAP) {
50
+ return <OneTap />;
51
+ }
52
+ if (type === AUTOMATE_TYPE.VALUE_CHANGE) {
53
+ return <ValueChange />;
54
+ }
55
+ if ([AUTOMATE_TYPE.EVENT].includes(type)) {
56
+ return <Event />;
57
+ }
58
+ return <Schedule />;
59
+ };
60
+ const activateAt = activate_at
61
+ ? timeDifference(new Date(), moment(activate_at), true)
62
+ : null;
63
+ return (
64
+ <TouchableWithoutFeedback
65
+ onPress={onPressItem || goToDetail}
66
+ accessibilityLabel={`${AccessibilityLabel.AUTOMATE_SCRIPT_NAME}-${id}`}
67
+ >
68
+ <View style={[styles.container, wrapSyles]}>
69
+ <View style={styles.boxIcon}>
70
+ {displayIcon()}
71
+ {type === AUTOMATE_TYPE.ONE_TAP && (
72
+ <TouchableOpacity
73
+ accessibilityLabel={AccessibilityLabel.AUTOMATE_SCRIPT_ACTION}
74
+ onPress={handleScriptAction}
75
+ >
76
+ <CheckCircle />
77
+ </TouchableOpacity>
78
+ )}
79
+ </View>
80
+ <TouchableOpacity
81
+ accessibilityLabel={AccessibilityLabel.GO_DETAIL}
82
+ onPress={onPressItem || goToDetail}
83
+ >
84
+ <Text
85
+ numberOfLines={1}
86
+ semibold
87
+ size={14}
88
+ color={Colors.Gray9}
89
+ type="Body"
90
+ style={styles.name}
84
91
  >
92
+ {script?.name}
93
+ </Text>
94
+ <Text numberOfLines={1} type={'Label'} color={Colors.Gray7}>
95
+ {t('created_by', { name: author })}
96
+ </Text>
97
+ <View style={styles.descriptionContainer}>
85
98
  <Text
86
99
  numberOfLines={1}
87
100
  semibold
88
- size={14}
89
- color={Colors.Gray9}
90
- type="Body"
91
- style={styles.name}
101
+ size={12}
102
+ color={Colors.Gray8}
103
+ type="Label"
92
104
  >
93
- {script?.name}
105
+ {activateAt && t('activated_time', { time: activateAt })}
94
106
  </Text>
95
- <Text numberOfLines={1} type={'Label'} color={Colors.Gray7}>
96
- {`${t('create_by')} ${author}`}
97
- </Text>
98
- <View style={styles.descriptionContainer}>
99
- <Text
100
- numberOfLines={1}
101
- semibold
102
- size={12}
103
- color={Colors.Gray8}
104
- type="Label"
105
- >
106
- {activateAt && t('activated_time', { time: activateAt })}
107
- </Text>
108
- <IconOutline name="right" size={12} />
109
- </View>
110
- </TouchableOpacity>
111
- </View>
112
- </TouchableWithoutFeedback>
113
- );
114
- }
115
- );
107
+ <IconOutline name="right" size={12} />
108
+ </View>
109
+ </TouchableOpacity>
110
+ </View>
111
+ </TouchableWithoutFeedback>
112
+ );
113
+ });
116
114
 
117
115
  export default ItemOneTap;
@@ -16,11 +16,13 @@ import Routes from '../../../../utils/Route';
16
16
  import api from '../../../../utils/Apis/axios';
17
17
  import { API } from '../../../../configs';
18
18
  import { useNavigation } from '@react-navigation/native';
19
+ import { ToastBottomHelper } from '../../../../utils/Utils';
20
+ import { getTranslate } from '../../../../utils/I18n';
19
21
 
20
22
  const mock = new MockAdapter(api.axiosInstance);
21
23
 
22
- const wrapComponent = (data) => (
23
- <SCProvider initState={mockSCStore({})}>
24
+ const wrapComponent = (data, storeData = {}) => (
25
+ <SCProvider initState={mockSCStore(storeData)}>
24
26
  <SubUnitAutomate {...data} />
25
27
  </SCProvider>
26
28
  );
@@ -33,33 +35,34 @@ jest.mock('react-redux', () => {
33
35
  });
34
36
 
35
37
  let tree;
36
- let data = {
37
- isOwner: true,
38
- listAutomate: [
39
- {
40
- text: 'Scenario',
41
- data: [
42
- {
43
- id: 1,
44
- user: 6,
45
- type: 'one_tap',
46
- activate_at: '2021-09-17T05:30:00Z',
47
- script: {
48
- name: 'Joshua Ray',
49
- icon: '',
50
- icon_kit: '',
51
- },
52
- },
53
- ],
54
- type: AUTOMATE_TABS.SCENARIO,
55
- },
56
- ],
57
- };
38
+ let data;
58
39
 
59
40
  describe('test Item', () => {
60
41
  const mockedNavigate = useNavigation().navigate;
61
42
  beforeEach(() => {
62
43
  mockedNavigate.mockClear();
44
+ data = {
45
+ isOwner: true,
46
+ listAutomate: [
47
+ {
48
+ text: 'Scenario',
49
+ data: [
50
+ {
51
+ id: 1,
52
+ user: 6,
53
+ type: 'one_tap',
54
+ activate_at: '2021-09-17T05:30:00Z',
55
+ script: {
56
+ name: 'Joshua Ray',
57
+ icon: '',
58
+ icon_kit: '',
59
+ },
60
+ },
61
+ ],
62
+ type: AUTOMATE_TABS.SCENARIO,
63
+ },
64
+ ],
65
+ };
63
66
  });
64
67
 
65
68
  it('render SubUnitAutomate isOwner and handleOnAddNew', async () => {
@@ -189,6 +192,7 @@ describe('test Item', () => {
189
192
  preAutomate: data.listAutomate[0].data[0],
190
193
  });
191
194
  });
195
+
192
196
  it('render SubUnitAutomate script schedule and handleOnAddNew item automate', async () => {
193
197
  data.isOwner = false;
194
198
  data.type = 'schedule';
@@ -242,6 +246,46 @@ describe('test Item', () => {
242
246
  expect(goDetail).toHaveLength(1);
243
247
  });
244
248
 
249
+ it('add new automate when reach maximum', async () => {
250
+ data.isOwner = false;
251
+ data.type = 'schedule';
252
+ data.unit = { id: 1 };
253
+
254
+ await act(async () => {
255
+ tree = await create(
256
+ wrapComponent(data, {
257
+ auth: {
258
+ account: {
259
+ user: {
260
+ permissions: {
261
+ max_automations_per_unit: 0,
262
+ },
263
+ },
264
+ },
265
+ },
266
+ })
267
+ );
268
+ });
269
+
270
+ const instance = tree.root;
271
+ const item = instance.findAll(
272
+ (el) =>
273
+ el.props.accessibilityLabel ===
274
+ AccessibilityLabel.SUB_UNIT_ADD_DEVICE &&
275
+ el.type === TouchableWithoutFeedback
276
+ );
277
+
278
+ expect(item).toHaveLength(1);
279
+ const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
280
+ await act(async () => {
281
+ await item[0].props.onPress();
282
+ });
283
+ expect(mockedNavigate).not.toBeCalled();
284
+ expect(spyToastError).toBeCalledWith(
285
+ getTranslate('en', 'reach_max_automations_per_unit')
286
+ );
287
+ });
288
+
245
289
  it('render click select option by automation', async () => {
246
290
  await act(async () => {
247
291
  tree = await create(wrapComponent(data));
@@ -1,4 +1,10 @@
1
- import React, { useCallback, useEffect, useRef, useState } from 'react';
1
+ import React, {
2
+ useCallback,
3
+ useEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
2
8
  import { TouchableOpacity, View } from 'react-native';
3
9
 
4
10
  import { Section } from '../..';
@@ -15,15 +21,29 @@ import {
15
21
  import Text from '../../Text/index.js';
16
22
 
17
23
  import styles from './OneTapStyles.js';
24
+ import { ToastBottomHelper } from '../../../utils/Utils';
25
+ import { useBackendPermission } from '../../../utils/Permission/backend';
18
26
 
19
27
  const SubUnitAutomate = ({ isOwner, listAutomate, unit, wrapItemStyle }) => {
20
28
  const t = useTranslations();
21
29
  const { navigate } = useNavigation();
22
30
  const [automates, setAutomates] = useState(listAutomate[0]);
23
31
  const [indexAutomate, setIndexAutomate] = useState(0);
32
+ const permissions = useBackendPermission();
24
33
  const { name: currentScreen } = useRoute();
25
34
 
35
+ const totalAutomates = useMemo(
36
+ () => listAutomate.reduce((acc, item) => item.data.length + acc, 0),
37
+ [listAutomate]
38
+ );
39
+
26
40
  const handleOnAddNew = () => {
41
+ if (unit?.id) {
42
+ if (permissions.max_automations_per_unit <= totalAutomates) {
43
+ ToastBottomHelper.error(t('reach_max_automations_per_unit'));
44
+ return;
45
+ }
46
+ }
27
47
  switch (automates.type) {
28
48
  case AUTOMATE_TABS.SCENARIO:
29
49
  navigate(Routes.ScenarioName, {
@@ -215,6 +215,7 @@ const API = {
215
215
  },
216
216
  GATEWAY: {
217
217
  LIST: () => '/chip_manager/developer_mode_chips/',
218
+ COUNT: () => '/chip_manager/developer_mode_chips/count/',
218
219
  DETAIL: (id) => `/chip_manager/developer_mode_chips/${id}/`,
219
220
  REBOOT: (id) => `/chip_manager/developer_mode_chips/${id}/reboot_chip/`,
220
221
  },
@@ -33,8 +33,8 @@ jest.mock('react', () => {
33
33
 
34
34
  const mock = new MockAdapter(api.axiosInstance);
35
35
 
36
- const wrapComponent = (route) => (
37
- <SCProvider initState={mockSCStore({})}>
36
+ const wrapComponent = (route, storeData = {}) => (
37
+ <SCProvider initState={mockSCStore(storeData)}>
38
38
  <ActivityLog route={route} />
39
39
  </SCProvider>
40
40
  );
@@ -149,4 +149,62 @@ describe('Test Activity log', () => {
149
149
  expect(filterPopup[0].props.isVisible).toBeTruthy();
150
150
  expect(datePicker.props.isVisible).toBeFalsy();
151
151
  });
152
+
153
+ const setUpActionLogScreen = () => {
154
+ route = {
155
+ params: {
156
+ id: 1,
157
+ type: 'action',
158
+ filterEnabled: {},
159
+ },
160
+ };
161
+
162
+ mock.onGet(API.DEVICE.ACTIVITY_LOG(1)).reply(200, {
163
+ results: [
164
+ {
165
+ id: 1,
166
+ content_code: 'ACTIVATED_BY',
167
+ params: { username: 'name' },
168
+ created_at: '2021-07-01T15:48:24.917932Z',
169
+ },
170
+ ],
171
+ count: 1,
172
+ });
173
+ global.URLSearchParams = jest.fn(() => ({
174
+ append: jest.fn(),
175
+ }));
176
+ };
177
+
178
+ it('render action log has permission', async () => {
179
+ const storeData = {
180
+ auth: {
181
+ account: {
182
+ user: {
183
+ permissions: {
184
+ view_action_log: true,
185
+ },
186
+ },
187
+ },
188
+ },
189
+ };
190
+ setUpActionLogScreen();
191
+
192
+ await act(async () => {
193
+ tree = await create(wrapComponent(route, storeData));
194
+ });
195
+ const instance = tree.root;
196
+ const items = instance.findAllByType(ItemLog);
197
+ expect(items).toHaveLength(1);
198
+ });
199
+
200
+ it('render action log no permission', async () => {
201
+ setUpActionLogScreen();
202
+
203
+ await act(async () => {
204
+ tree = await create(wrapComponent(route));
205
+ });
206
+ const instance = tree.root;
207
+ const items = instance.findAllByType(ItemLog);
208
+ expect(items).toHaveLength(0);
209
+ });
152
210
  });
@@ -7,6 +7,11 @@ import api from '../../../../utils/Apis/axios';
7
7
  import { getDataForList, getEmergencyEventDataForList } from '../../utils';
8
8
 
9
9
  const mock = new MockAdapter(api.axiosInstance);
10
+ jest.mock('../../../../utils/Permission/backend', () => ({
11
+ useBackendPermission: jest.fn(() => ({
12
+ view_action_log: true,
13
+ })),
14
+ }));
10
15
 
11
16
  describe('Test useActivityLog', () => {
12
17
  let props;
@@ -6,6 +6,8 @@ import API from '../../../configs/API';
6
6
  import t from '../../../hooks/Common/useTranslations';
7
7
  import { AUTOMATE_TYPE } from '../../../configs/Constants';
8
8
  import { getDataForList, getEmergencyEventDataForList } from '../utils';
9
+ import { useBackendPermission } from '../../../utils/Permission/backend';
10
+ import { ToastBottomHelper } from '../../../utils/Utils';
9
11
 
10
12
  const apiMaps = {
11
13
  ['action']: {
@@ -13,6 +15,7 @@ const apiMaps = {
13
15
  params: (id) => ({ id: id }),
14
16
  standardizeData: getDataForList,
15
17
  memberUrl: (id) => API.SHARE.UNITS_MEMBERS(id),
18
+ needPermission: 'view_action_log',
16
19
  },
17
20
  ['emergency_event']: {
18
21
  url: () => API.EMERGENCY_BUTTON.ACTIVITY_LOG(),
@@ -54,7 +57,15 @@ export default ({ id, type, share, filterEnabled }) => {
54
57
  });
55
58
  const api = apiMaps[type];
56
59
 
60
+ const permissions = useBackendPermission();
57
61
  const fetchData = async (filters) => {
62
+ if (api?.needPermission && !permissions?.[api?.needPermission]) {
63
+ setIsLoading(false);
64
+ setIsRefreshing(false);
65
+ ToastBottomHelper.error(t(`no_permission_${api?.needPermission}`));
66
+ return;
67
+ }
68
+
58
69
  const { page } = filters;
59
70
  setPage(page);
60
71
  if (page === 1) {
@@ -287,7 +287,7 @@ const AddLocationMaps = memo(() => {
287
287
  onPressMain={onDone}
288
288
  secondaryTitle={t('cancel')}
289
289
  onPressSecondary={goBack}
290
- typeMain={input?.description ? 'primaryText' : 'disabled'}
290
+ typeMain={searchedLocation?.description ? 'primaryText' : 'disabled'}
291
291
  typeSecondary="primaryText"
292
292
  accessibilityLabelPrefix="LOCATION_"
293
293
  disableBackgroundMainButton