@eohjsc/react-native-smart-city 0.3.94 → 0.3.96

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 (29) 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/BottomButtonView/index.js +3 -1
  6. package/src/commons/ButtonPopup/index.js +1 -0
  7. package/src/commons/SelectGateway/index.js +1 -0
  8. package/src/commons/SelectGateway/styles.js +3 -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/ConnectingWifiDevice.js +6 -4
  14. package/src/screens/AddNewGateway/ConnectingWifiGuide.js +0 -1
  15. package/src/screens/AddNewGateway/ScanModbusQR.js +4 -0
  16. package/src/screens/AddNewGateway/ScanWifiDeviceQR.js +1 -6
  17. package/src/screens/AddNewGateway/ShareWifiPassword.js +20 -3
  18. package/src/screens/AddNewGateway/__test__/ScanWifiDeviceQR.test.js +0 -10
  19. package/src/screens/AllGateway/GatewayInfo/index.js +3 -3
  20. package/src/screens/Automate/AddNewAction/SetupConfigCondition.js +0 -1
  21. package/src/screens/Automate/MultiUnits.js +4 -0
  22. package/src/screens/Automate/__test__/MultiUnits.test.js +62 -2
  23. package/src/screens/Automate/__test__/index.test.js +51 -1
  24. package/src/screens/Automate/index.js +10 -1
  25. package/src/screens/ScanChipQR/components/InvalidQRCode/index.js +4 -0
  26. package/src/screens/SubUnit/AddSubUnit.js +1 -1
  27. package/src/screens/SubUnit/__test__/AddSubUnit.test.js +16 -3
  28. package/src/utils/I18n/translations/en.js +13 -7
  29. package/src/utils/I18n/translations/vi.js +13 -4
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.94",
4
+ "version": "0.3.96",
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
 
@@ -23,6 +23,7 @@ const BottomButtonView = memo(
23
23
  accessibilityLabelPrefix = '',
24
24
  disableBackgroundMainButton = false,
25
25
  disableBackgroundSecondButton = false,
26
+ disableKeyBoardAnimated = false,
26
27
  }) => {
27
28
  const transY = useKeyboardAnimated();
28
29
  const [keyboardAnim] = useState(new Animated.Value(0));
@@ -42,7 +43,8 @@ const BottomButtonView = memo(
42
43
  typeMain === 'CardShadow'
43
44
  ? styleCustom.container1
44
45
  : styleCustom.container,
45
- { bottom: keyboardAnim },
46
+ // eslint-disable-next-line react-native/no-inline-styles
47
+ { bottom: disableKeyBoardAnimated ? 0.1 : keyboardAnim },
46
48
  rowButton && styleCustom.horizontalContainer,
47
49
  style,
48
50
  ]}
@@ -61,6 +61,7 @@ const ButtonPopup = ({
61
61
  typeSecondary={typeSecondary}
62
62
  accessibilityLabelPrefix={AccessibilityLabel.PREFIX.BUTTON_POPUP}
63
63
  semiboldSecond={semiboldSecond}
64
+ disableKeyBoardAnimated
64
65
  />
65
66
  {thirdTitle && (
66
67
  <BottomButtonView
@@ -140,6 +140,7 @@ const SelectGateway = ({
140
140
  rightTitle={t('ok')}
141
141
  onRightClick={handleButtonModalOk}
142
142
  styleButton={styles.bottomButton}
143
+ wrapStyle={styles.wrapViewButton}
143
144
  />
144
145
  </View>
145
146
  </ModalCustom>
@@ -73,4 +73,7 @@ export default StyleSheet.create({
73
73
  bottomButton: {
74
74
  paddingVertical: 11,
75
75
  },
76
+ wrapViewButton: {
77
+ position: 'relative',
78
+ },
76
79
  });
@@ -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
@@ -12,7 +12,7 @@ let isCallingAPI = false;
12
12
 
13
13
  const ConnectingWifiDevice = ({ route }) => {
14
14
  const t = useTranslations();
15
- const { unit, subUnit, gateway, selectedWifi, qrData, addDeviceType } =
15
+ const { unit, subUnit, gateway, selectedWifi, qrData, addDeviceType, code } =
16
16
  route?.params || {};
17
17
  const { goBack } = useNavigation();
18
18
 
@@ -40,7 +40,8 @@ const ConnectingWifiDevice = ({ route }) => {
40
40
  const { success, problem, data } = await axiosPost(
41
41
  API.UNIT.CHIP_SCAN(unit?.id),
42
42
  {
43
- imei: gateway?.imei,
43
+ code,
44
+ host: qrData?.host,
44
45
  secret: qrData?.secret,
45
46
  name: gateway?.model,
46
47
  station: subUnit?.id,
@@ -61,9 +62,10 @@ const ConnectingWifiDevice = ({ route }) => {
61
62
  },
62
63
  [
63
64
  unit?.id,
64
- gateway?.imei,
65
- gateway?.model,
65
+ code,
66
+ qrData?.host,
66
67
  qrData?.secret,
68
+ gateway?.model,
67
69
  subUnit?.id,
68
70
  selectedWifi.ssid,
69
71
  selectedWifi.password,
@@ -124,7 +124,6 @@ const ConnectingWifiGuide = ({ route }) => {
124
124
  socket = dgram.createSocket({ type: 'udp4' });
125
125
  socket.bind(54321);
126
126
  }
127
-
128
127
  socket.on('message', (msg, rinfo) => {
129
128
  const data = JSON.parse(msg.toString());
130
129
  if (Object.prototype.hasOwnProperty.call(data, 'wifi')) {
@@ -84,6 +84,7 @@ const ScanModbusQR = memo(({ route }) => {
84
84
  onLeftClick={handleCancel}
85
85
  rightTitle={t('ok')}
86
86
  onRightClick={setHidePopupGuide}
87
+ wrapStyle={styles.wrapViewButton}
87
88
  />
88
89
  </View>
89
90
  </ModalCustom>
@@ -119,6 +120,9 @@ const styles = StyleSheet.create({
119
120
  txtCenter: {
120
121
  textAlign: 'center',
121
122
  },
123
+ wrapViewButton: {
124
+ position: 'relative',
125
+ },
122
126
  });
123
127
 
124
128
  export default ScanModbusQR;
@@ -9,7 +9,6 @@ const ScanWifiDeviceQR = memo(({ route }) => {
9
9
  const { unit, subUnit } = route?.params || {};
10
10
  const [isInvalidQrCode, setIsInvalidQrCode] = useState(false);
11
11
  const { navigate, goBack } = useNavigation();
12
-
13
12
  const onScan = useCallback(
14
13
  (body, setLoading) => {
15
14
  let data;
@@ -20,11 +19,7 @@ const ScanWifiDeviceQR = memo(({ route }) => {
20
19
  setLoading(false);
21
20
  return;
22
21
  }
23
- if (data?.prefix !== 'robot') {
24
- setIsInvalidQrCode(true);
25
- setLoading(false);
26
- return;
27
- }
22
+
28
23
  navigate(Routes.ConnectingWifiGuide, {
29
24
  unit,
30
25
  subUnit,
@@ -1,4 +1,10 @@
1
- import React, { useCallback, useRef, useState, useEffect } from 'react';
1
+ import React, {
2
+ useCallback,
3
+ useRef,
4
+ useState,
5
+ useEffect,
6
+ useMemo,
7
+ } from 'react';
2
8
  import {
3
9
  Alert,
4
10
  TouchableOpacity,
@@ -9,6 +15,7 @@ import {
9
15
  import dgram from 'react-native-udp';
10
16
  import { useNavigation, useIsFocused } from '@react-navigation/native';
11
17
  import LottieView from 'lottie-react-native';
18
+ import uuid from 'uuid';
12
19
 
13
20
  import { ToastBottomHelper } from '../../utils/Utils';
14
21
  import { useTranslations } from '../../hooks/Common/useTranslations';
@@ -68,6 +75,10 @@ const ShareWifiPassword = ({ route }) => {
68
75
  const [isDisabled, setIsDisabled] = useState(false);
69
76
  const [dataGateway, setDataGateway] = useState({ gateway: [], error: '' });
70
77
 
78
+ const code = useMemo(() => {
79
+ return uuid.v4();
80
+ }, []);
81
+
71
82
  const hidePopupPassword = useCallback(() => {
72
83
  setIsShowPopupPassword(false);
73
84
  }, []);
@@ -80,7 +91,11 @@ const ShareWifiPassword = ({ route }) => {
80
91
  socket.send(
81
92
  JSON.stringify({
82
93
  type: 'connect',
83
- data: { wifi: { ssid: selectedWifi, pass: selectedWifiPassword } },
94
+ data: {
95
+ token: code,
96
+ host: qrData?.host,
97
+ wifi: { ssid: selectedWifi, pass: selectedWifiPassword },
98
+ },
84
99
  }),
85
100
  undefined,
86
101
  undefined,
@@ -94,7 +109,7 @@ const ShareWifiPassword = ({ route }) => {
94
109
  }
95
110
  }, 1000);
96
111
  },
97
- [selectedWifiPassword, selectedWifi]
112
+ [code, qrData?.host, selectedWifi, selectedWifiPassword]
98
113
  );
99
114
 
100
115
  const isWrongPassword = useRef(true);
@@ -197,6 +212,7 @@ const ShareWifiPassword = ({ route }) => {
197
212
  password: selectedWifiPassword,
198
213
  },
199
214
  addDeviceType,
215
+ code,
200
216
  });
201
217
  }
202
218
  }, 800);
@@ -223,6 +239,7 @@ const ShareWifiPassword = ({ route }) => {
223
239
  t,
224
240
  wifiList,
225
241
  unit,
242
+ code,
226
243
  ]);
227
244
 
228
245
  return (
@@ -5,7 +5,6 @@ import ScanWifiDeviceQR from '../ScanWifiDeviceQR';
5
5
  import { SCProvider } from '../../../context';
6
6
  import { mockSCStore } from '../../../context/mockStore';
7
7
  import QRScan from '../../ScanChipQR/components/QRScan';
8
- import { getTranslate } from '../../../utils/I18n';
9
8
  import Routes from '../../../utils/Route';
10
9
 
11
10
  const mockedGoBack = jest.fn();
@@ -54,15 +53,6 @@ describe('test scan wifi device QR', () => {
54
53
  });
55
54
  };
56
55
 
57
- it('on scan wrong QR code', async () => {
58
- await scanQRCode({ imei: 'SAMSUNG-xxx' });
59
- const instance = tree.root;
60
- const bottomSheet = instance.findByProps({
61
- title: getTranslate('en', 'invalid_qr_code'),
62
- });
63
- expect(bottomSheet.props.isVisible).toBe(true);
64
- });
65
-
66
56
  it('on scan correct QR code', async () => {
67
57
  await scanQRCode({ prefix: 'robot', org_slug: 'eoh' }, jest.fn());
68
58
  expect(mockedNavigate).toHaveBeenCalled();
@@ -36,7 +36,7 @@ const GatewayInfo = () => {
36
36
  date_install = '',
37
37
  expired_at = '',
38
38
  firmware = {},
39
- imei = '',
39
+ code = '',
40
40
  arduino_gateway = undefined,
41
41
  modbus_gateway = undefined,
42
42
  zigbee_gateway = undefined,
@@ -81,7 +81,7 @@ const GatewayInfo = () => {
81
81
  {
82
82
  id: 5,
83
83
  title: 'auth_token',
84
- data: imei || '--',
84
+ data: code || '--',
85
85
  },
86
86
  {
87
87
  id: 7,
@@ -105,7 +105,7 @@ const GatewayInfo = () => {
105
105
  expired_at,
106
106
  firmware?.board,
107
107
  id,
108
- imei,
108
+ code,
109
109
  wifi_ssid,
110
110
  ]
111
111
  );
@@ -172,7 +172,6 @@ const SetupConfigCondition = () => {
172
172
  style={styles.modal}
173
173
  >
174
174
  <View style={styles.modalContent}>
175
- <Text type="H3">{t('select_condition')}</Text>
176
175
  {conditionOptions.map((option, index) => (
177
176
  <React.Fragment key={index}>
178
177
  <TouchableOpacity
@@ -81,6 +81,9 @@ const MultiUnits = () => {
81
81
  ToastBottomHelper.error(t('reach_max_automations_per_unit'));
82
82
  return;
83
83
  }
84
+ } else if (!permissions?.smart_script_for_multi_unit) {
85
+ ToastBottomHelper.error(t('no_permission_smart_script_for_multi_unit'));
86
+ return;
84
87
  }
85
88
  if (tabActive === AUTOMATE_TABS.SCENARIO) {
86
89
  navigate(Routes.UnitStack, {
@@ -105,6 +108,7 @@ const MultiUnits = () => {
105
108
  data.length,
106
109
  navigate,
107
110
  permissions?.max_automations_per_unit,
111
+ permissions?.smart_script_for_multi_unit,
108
112
  t,
109
113
  tabActive,
110
114
  unit?.id,
@@ -80,7 +80,19 @@ describe('Test MultiUnits', () => {
80
80
  },
81
81
  });
82
82
  await act(async () => {
83
- 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
+ );
84
96
  });
85
97
  const instance = tree.root;
86
98
  const WrapHeaderScrollables = instance.findAllByType(WrapHeaderScrollable);
@@ -157,7 +169,19 @@ describe('Test MultiUnits', () => {
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);
@@ -232,6 +256,42 @@ describe('Test MultiUnits', () => {
232
256
  );
233
257
  });
234
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
+
235
295
  it('Test is not multi unit', async () => {
236
296
  useRoute.mockReturnValue({
237
297
  params: {
@@ -81,7 +81,19 @@ describe('Test Automate', () => {
81
81
  mock.onGet(API.AUTOMATE.GET_SMART()).reply(200, response.data);
82
82
 
83
83
  await act(async () => {
84
- 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
+ );
85
97
  });
86
98
 
87
99
  const instance = tree.root;
@@ -245,4 +257,42 @@ describe('Test Automate', () => {
245
257
  unit: { id: 3, name: 'La Vida' },
246
258
  });
247
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
+ });
248
298
  });
@@ -88,6 +88,9 @@ const Automate = () => {
88
88
  ToastBottomHelper.error(t('reach_max_automations_per_unit'));
89
89
  return;
90
90
  }
91
+ } else if (!permissions?.smart_script_for_multi_unit) {
92
+ ToastBottomHelper.error(t('no_permission_smart_script_for_multi_unit'));
93
+ return;
91
94
  }
92
95
  navigate(Routes.UnitStack, {
93
96
  screen: Routes.AddUnknownTypeSmart,
@@ -97,7 +100,13 @@ const Automate = () => {
97
100
  },
98
101
  });
99
102
  },
100
- [currentRouteName, navigate, permissions?.max_automations_per_unit, t]
103
+ [
104
+ currentRouteName,
105
+ navigate,
106
+ permissions?.max_automations_per_unit,
107
+ permissions?.smart_script_for_multi_unit,
108
+ t,
109
+ ]
101
110
  );
102
111
 
103
112
  const onPressArrowRight = useCallback(
@@ -19,6 +19,7 @@ const InvalidQRCode = ({ isInvalidQrCode, goBack, onRetry }) => {
19
19
  onLeftClick={goBack}
20
20
  rightTitle={t('retry')}
21
21
  onRightClick={onRetry}
22
+ wrapStyle={styles.wrapViewButton}
22
23
  />
23
24
  </BottomSheet>
24
25
  );
@@ -30,4 +31,7 @@ const styles = StyleSheet.create({
30
31
  warningContainer: {
31
32
  paddingHorizontal: 16,
32
33
  },
34
+ wrapViewButton: {
35
+ position: 'relative',
36
+ },
33
37
  });
@@ -163,7 +163,7 @@ const AddSubUnit = ({ route }) => {
163
163
 
164
164
  const validateData = useMemo(() => {
165
165
  if (isAddUnit) {
166
- return roomName === '' || wallpaper === '' || location.description === '';
166
+ return roomName === '' || wallpaper === '' || !location.description;
167
167
  } else {
168
168
  return roomName === '' || wallpaper === '';
169
169
  }
@@ -62,6 +62,9 @@ describe('Test AddSubUnit', () => {
62
62
  unit: {
63
63
  id: 1,
64
64
  name: 'Unit name',
65
+ location: {
66
+ description: 'location',
67
+ },
65
68
  },
66
69
  },
67
70
  };
@@ -187,7 +190,9 @@ describe('Test AddSubUnit', () => {
187
190
  it('test create Unit', async () => {
188
191
  route.params = {
189
192
  ...route.params,
190
- location: 'Unit address',
193
+ location: {
194
+ description: 'Unit address',
195
+ },
191
196
  isAddUnit: true,
192
197
  };
193
198
  mock.onPost(API.UNIT.CREATE_UNIT()).reply(200, {
@@ -231,7 +236,9 @@ describe('Test AddSubUnit', () => {
231
236
  it('test create Unit Fail', async () => {
232
237
  route.params = {
233
238
  ...route.params,
234
- location: 'Unit address',
239
+ location: {
240
+ description: 'Unit address',
241
+ },
235
242
  isAddUnit: true,
236
243
  };
237
244
  mock.onPost(API.UNIT.CREATE_UNIT()).reply(400);
@@ -270,7 +277,13 @@ describe('Test AddSubUnit', () => {
270
277
  isAddSubUnit: true,
271
278
  routeName: 'DashboardStack',
272
279
  stationId: undefined,
273
- unitData: { id: 1, name: 'Unit name' },
280
+ unitData: {
281
+ id: 1,
282
+ name: 'Unit name',
283
+ location: {
284
+ description: 'location',
285
+ },
286
+ },
274
287
  unitId: 1,
275
288
  },
276
289
  });
@@ -1126,9 +1126,6 @@ export default {
1126
1126
  'To give you a good experience, you need to know the name and password of the wifi network and make sure the ' +
1127
1127
  'device is placed in a good location.',
1128
1128
  warning_beta_test_feature: 'This feature is in beta test, not official yet',
1129
- connecting_gateway_warning_1:
1130
- "By continuing, {appName} needs to connect to the device's wifi.",
1131
- connecting_gateway_warning_2: 'A message will appear asking you to connect.',
1132
1129
  remove_account: 'Remove Account',
1133
1130
  brightness: 'Brightness',
1134
1131
  tap_to_add_new_schedule: 'Tap + to add new schedule',
@@ -1401,9 +1398,18 @@ export default {
1401
1398
  reach_max_members_per_unit: 'You have reached the maximum number of members',
1402
1399
  reach_max_chips_per_unit: 'You have reached the maximum number of gateway',
1403
1400
  reach_max_configs_per_unit: 'You have reached the maximum number of tags',
1404
- no_permission_plug_and_play_modbus: "You don't have permission to add modbus",
1405
- no_permission_plug_and_play_zigbee: "You don't have permission to add zigbee",
1406
- no_permission_plug_and_play_wifi: "You don't have permission to add wifi",
1401
+ no_permission_plug_and_play_modbus:
1402
+ 'Add modbus is currently not available on your Subscription. Please upgrade your plan for a better experience!',
1403
+ no_permission_plug_and_play_zigbee:
1404
+ 'Add zigbee is currently not available on your Subscription. Please upgrade your plan for a better experience!',
1405
+ no_permission_plug_and_play_wifi:
1406
+ 'Add wifi is currently not available on your Subscription. Please upgrade your plan for a better experience!',
1407
1407
  no_permission_plug_and_play_gateway:
1408
- "You don't have permission to add gateway",
1408
+ 'Add gateway is currently not available on your Subscription. Please upgrade your plan for a better experience!',
1409
+ no_permission_view_action_log:
1410
+ // eslint-disable-next-line max-len
1411
+ 'View action log is currently not available on your Subscription. Please upgrade your plan for a better experience!',
1412
+ no_permission_smart_script_for_multi_unit:
1413
+ // eslint-disable-next-line max-len
1414
+ 'Smart script for Multi Unit is currently not available on your Subscription. Please upgrade your plan for a better experience!',
1409
1415
  };
@@ -1404,8 +1404,17 @@ export default {
1404
1404
  reach_max_members_per_unit: 'Đã đạt đến số lượng tối đa thành viên',
1405
1405
  reach_max_chips_per_unit: 'Đã đạt đến số lượng tối đa chip',
1406
1406
  reach_max_configs_per_unit: 'Đã đạt đến số lượng tối đa thông số',
1407
- no_permission_plug_and_play_modbus: 'Không có quyền kết nối tự động modbus',
1408
- no_permission_plug_and_play_zigbee: 'Không có quyền kết nối tự động zigbee',
1409
- no_permission_plug_and_play_wifi: 'Không có quyền kết nối tự động wifi',
1410
- no_permission_plug_and_play_gateway: 'Không có quyền kết nối tự động gateway',
1407
+ no_permission_plug_and_play_modbus:
1408
+ 'Kết nối tự động modbus hiện tại chưa hỗ trợ trên gói của bạn. Vui lòng nâng cấp gói để có trải nghiệm tốt hơn!',
1409
+ no_permission_plug_and_play_zigbee:
1410
+ 'Kết nối tự động zigbee hiện tại chưa hỗ trợ trên gói của bạn. Vui lòng nâng cấp gói để có trải nghiệm tốt hơn!',
1411
+ no_permission_plug_and_play_wifi:
1412
+ 'Kết nối tự động wifi hiện tại chưa hỗ trợ trên gói của bạn. Vui lòng nâng cấp gói để có trải nghiệm tốt hơn!',
1413
+ no_permission_plug_and_play_gateway:
1414
+ 'Kết nối tự động gateway hiện tại chưa hỗ trợ trên gói của bạn. Vui lòng nâng cấp gói để có trải nghiệm tốt hơn!',
1415
+ no_permission_view_action_log:
1416
+ 'Xem lịch sử hoạt động hiện tại chưa hỗ trợ trên gói của bạn. Vui lòng nâng cấp gói để có trải nghiệm tốt hơn!',
1417
+ no_permission_smart_script_for_multi_unit:
1418
+ // eslint-disable-next-line max-len
1419
+ 'Kịch bản thông minh cho nhiều địa điểm hiện tại chưa hỗ trợ trên gói của bạn. Vui lòng nâng cấp gói để có trải nghiệm tốt hơn!',
1411
1420
  };