@eohjsc/react-native-smart-city 0.2.95 → 0.2.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 (50) hide show
  1. package/package.json +2 -2
  2. package/src/Images/Common/member.svg +4 -0
  3. package/src/Images/Common/owner.svg +3 -0
  4. package/src/commons/ActionGroup/NumberUpDownActionTemplate.js +5 -1
  5. package/src/commons/ActionGroup/OptionsDropdownActionTemplate.js +5 -1
  6. package/src/commons/ActionGroup/__test__/OptionsDropdownTemplate.test.js +2 -2
  7. package/src/commons/Connecting /__test__/Connecting.test.js +23 -0
  8. package/src/commons/Connecting /index.js +67 -0
  9. package/src/commons/Connecting /styles.js +28 -0
  10. package/src/commons/ConnectingProcess/index.js +3 -54
  11. package/src/commons/MenuActionMore/index.js +8 -1
  12. package/src/commons/Sharing/MemberList.js +5 -10
  13. package/src/commons/Sharing/RowMember.js +128 -38
  14. package/src/commons/Sharing/__test__/MemberList.test.js +3 -3
  15. package/src/commons/Sharing/__test__/RowMember.test.js +4 -4
  16. package/src/configs/API.js +10 -0
  17. package/src/configs/Constants.js +10 -0
  18. package/src/hooks/Common/useSensorsStatus.js +33 -23
  19. package/src/navigations/UnitStack.js +16 -0
  20. package/src/screens/AddNewGateway/PlugAndPlay/ConnectWifiWarning.js +12 -3
  21. package/src/screens/AddNewGateway/PlugAndPlay/FirstWarning.js +5 -1
  22. package/src/screens/AddNewGateway/PlugAndPlay/GatewayWifiList.js +9 -1
  23. package/src/screens/EditActionsList/Styles/indexStyles.js +1 -0
  24. package/src/screens/EmergencyContacts/EmergencyContactsList.js +1 -1
  25. package/src/screens/EmergencyContacts/EmergencyContactsSelectContacts.js +29 -11
  26. package/src/screens/EmergencyContacts/__test__/EmergencyContactList.test.js +1 -0
  27. package/src/screens/EmergencyContacts/__test__/EmergencyContactsSelectContacts.test.js +56 -0
  28. package/src/screens/EnterPassword/__test__/EnterPassword.test.js +124 -0
  29. package/src/screens/EnterPassword/index.js +84 -0
  30. package/src/screens/EnterPassword/styles.js +36 -0
  31. package/src/screens/Sharing/Components/ItemChangeRole.js +43 -0
  32. package/src/screens/Sharing/Components/SensorItem.js +7 -1
  33. package/src/screens/Sharing/Components/Styles/ItemChangeRoleStyles.js +35 -0
  34. package/src/screens/Sharing/Components/__test__/ItemChangeRole.test.js +37 -0
  35. package/src/screens/Sharing/Components/__test__/SensorItem.test.js +53 -0
  36. package/src/screens/Sharing/InfoMemberUnit.js +274 -0
  37. package/src/screens/Sharing/MemberList.js +50 -53
  38. package/src/screens/Sharing/SelectPermission.js +93 -12
  39. package/src/screens/Sharing/Styles/inforMemberUnitStyles.js +92 -0
  40. package/src/screens/Sharing/__test__/InfoMemberUnit.test.js +121 -0
  41. package/src/screens/Sharing/__test__/MemberList.test.js +9 -24
  42. package/src/screens/Sharing/__test__/SelectPermission.test.js +53 -0
  43. package/src/screens/Sharing/hooks/index.js +76 -32
  44. package/src/screens/Unit/Detail.js +12 -10
  45. package/src/screens/Unit/ManageUnit.js +5 -5
  46. package/src/screens/Unit/ManageUnitStyles.js +1 -0
  47. package/src/utils/I18n/translations/en.json +13 -1
  48. package/src/utils/I18n/translations/vi.json +11 -1
  49. package/src/utils/Route/index.js +2 -0
  50. package/src/utils/Utils.js +21 -0
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useMemo, useState } from 'react';
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import {
3
3
  SafeAreaView,
4
4
  View,
@@ -13,7 +13,9 @@ import { useTranslations } from '../../hooks/Common/useTranslations';
13
13
  import { API, Colors } from '../../configs';
14
14
  import ViewButtonBottom from '../../commons/ViewButtonBottom';
15
15
  import Text from '../../commons/Text';
16
- import { axiosGet } from '../../utils/Apis/axios';
16
+ import { axiosGet, axiosPost } from '../../utils/Apis/axios';
17
+ import { object_Ids } from '../../utils/Utils';
18
+
17
19
  import Routes from '../../utils/Route';
18
20
  import { SensorItem, TitleCheckBox } from './Components';
19
21
  import styles from './Styles/SelectPermissionStyles';
@@ -24,12 +26,14 @@ let dataStationTemp = [];
24
26
 
25
27
  const SelectPermission = ({ route }) => {
26
28
  const t = useTranslations();
27
- const { unit } = route.params;
29
+ const { unit, type = '', member } = route.params;
28
30
  const navigation = useNavigation();
29
31
  const [dataStations, setDataStations] = useState([]);
30
32
  const [isTickAllDevices, setIsTickAllDevices] = useState(false);
31
33
  const [activeItemId, setActiveItemId] = useState(-1);
32
34
  const [loading, setLoading] = useState(true);
35
+ const [dataDeviceShared, setDataDeviceShared] = useState([]);
36
+ const [hasDataChecked, setHasDataChecked] = useState(false);
33
37
 
34
38
  const onTickTitle = (idGroup, isChecked, id) => {
35
39
  if (!idGroup) {
@@ -121,6 +125,42 @@ const SelectPermission = ({ route }) => {
121
125
  setDataStations(data);
122
126
  };
123
127
 
128
+ const autoCheckedGroup = useCallback(async () => {
129
+ if (type === 'share_device') {
130
+ if (hasDataChecked) {
131
+ const stationIds = object_Ids(dataDeviceShared)?.stationIds;
132
+ const sensorIds = object_Ids(dataDeviceShared)?.sensorIds;
133
+ const actionIds = object_Ids(dataDeviceShared)?.actionIds;
134
+ const configIds = object_Ids(dataDeviceShared)?.configIds;
135
+ const data = dataStations?.map((i) => ({
136
+ ...i,
137
+ isChecked: stationIds.includes(i.id),
138
+ }));
139
+ for (let station in data) {
140
+ for (let item in data[station].sensors) {
141
+ const itemTemp = data[station].sensors[item];
142
+ data[station].sensors[item] = {
143
+ ...itemTemp,
144
+ isChecked: sensorIds.includes(itemTemp?.id),
145
+ actions: itemTemp.actions.map((i) => ({
146
+ ...i,
147
+ isChecked: actionIds?.includes(i.id),
148
+ })),
149
+ read_configs: itemTemp.read_configs.map((i) => ({
150
+ ...i,
151
+ isChecked: configIds?.includes(i.id),
152
+ })),
153
+ };
154
+ }
155
+ }
156
+ const sensorIdsDefault = object_Ids(data)?.sensorIds;
157
+ setDataStations(data);
158
+ setIsTickAllDevices(sensorIdsDefault?.length === sensorIds?.length);
159
+ setHasDataChecked(false);
160
+ }
161
+ }
162
+ }, [dataDeviceShared, dataStations, hasDataChecked, type]);
163
+
124
164
  const GroupSenSorItem = ({ item = {}, index }) => {
125
165
  const { name = '', sensors = [], isChecked, id = '' } = item;
126
166
  return (
@@ -152,7 +192,7 @@ const SelectPermission = ({ route }) => {
152
192
  );
153
193
  };
154
194
 
155
- const onPressNext = () => {
195
+ const onPressBottom = async () => {
156
196
  let read_permissions = [],
157
197
  control_permissions = [];
158
198
  for (let station of dataStationTemp) {
@@ -179,10 +219,28 @@ const SelectPermission = ({ route }) => {
179
219
  Alert.alert('', t('choose_at_least_one'));
180
220
  return;
181
221
  }
182
- navigation.navigate(Routes.SharingInviteMembers, {
183
- unit,
184
- permissions: { read_permissions, control_permissions },
185
- });
222
+ if (type === 'share_device') {
223
+ const phone =
224
+ member?.phone_number && member?.email
225
+ ? member?.phone_number
226
+ : member?.phone_number || '';
227
+ const email = member?.phone_number ? '' : member?.email || '';
228
+
229
+ const { success } = await axiosPost(API.SHARE.SHARE(), {
230
+ phone,
231
+ email,
232
+ unit: unit?.id,
233
+ permissions: { read_permissions, control_permissions },
234
+ });
235
+ if (success) {
236
+ navigation.goBack();
237
+ }
238
+ } else {
239
+ navigation.navigate(Routes.SharingInviteMembers, {
240
+ unit,
241
+ permissions: { read_permissions, control_permissions },
242
+ });
243
+ }
186
244
  };
187
245
 
188
246
  const renderGroupItem = ({ item }) => (
@@ -198,7 +256,7 @@ const SelectPermission = ({ route }) => {
198
256
  data={dataStations}
199
257
  renderItem={renderGroupItem}
200
258
  ListHeaderComponent={
201
- Boolean(dataStations.length) && (
259
+ Boolean(dataStations?.length) && (
202
260
  <TitleCheckBox
203
261
  title={t('text_all_devices')}
204
262
  wrapStyle={styles.wrapAllDevices}
@@ -214,6 +272,29 @@ const SelectPermission = ({ route }) => {
214
272
  // eslint-disable-next-line react-hooks/exhaustive-deps
215
273
  }, [dataStations, isTickAllDevices, activeItemId]);
216
274
 
275
+ const getShareUnitPermission = useCallback(async () => {
276
+ if (type === 'share_device') {
277
+ if (dataStations?.length > 0) {
278
+ const response = await axiosGet(
279
+ API.SHARE.UNIT_MEMBER_SHARE_DEVICE(unit?.id, member?.id)
280
+ );
281
+ if (response.success) {
282
+ setDataDeviceShared(response.data);
283
+ setHasDataChecked(true);
284
+ }
285
+ }
286
+ setLoading(false);
287
+ }
288
+ }, [dataStations?.length, member?.id, type, unit?.id]);
289
+
290
+ useEffect(() => {
291
+ autoCheckedGroup();
292
+ }, [autoCheckedGroup]);
293
+
294
+ useEffect(() => {
295
+ getShareUnitPermission();
296
+ }, [getShareUnitPermission]);
297
+
217
298
  useEffect(() => {
218
299
  (async () => {
219
300
  if (!unit) {
@@ -249,7 +330,7 @@ const SelectPermission = ({ route }) => {
249
330
  <View style={styles.contentContainer}>
250
331
  {loading ? (
251
332
  <ActivityIndicator color={Colors.Primary} />
252
- ) : dataStations.length > 0 ? (
333
+ ) : dataStations?.length > 0 ? (
253
334
  renderFlatList
254
335
  ) : (
255
336
  <Text style={styles.textNodata} testID={TESTID.TEXT_NO_DATA_STATIONS}>
@@ -260,9 +341,9 @@ const SelectPermission = ({ route }) => {
260
341
  testIDPrefix={TESTID.PREFIX.SHARING_SELECT_PERMISSION}
261
342
  leftTitle={t('cancel')}
262
343
  onLeftClick={() => navigation.goBack()}
263
- rightTitle={t('next')}
344
+ rightTitle={type === 'share_device' ? t('done') : t('next')}
264
345
  rightDisabled={false}
265
- onRightClick={onPressNext}
346
+ onRightClick={onPressBottom}
266
347
  />
267
348
  </View>
268
349
  </SafeAreaView>
@@ -0,0 +1,92 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { Colors } from '../../../configs';
3
+ import { getBottomSpace } from 'react-native-iphone-x-helper';
4
+
5
+ export default StyleSheet.create({
6
+ container: {
7
+ flex: 1,
8
+ backgroundColor: Colors.White,
9
+ },
10
+ userWrap: {
11
+ paddingVertical: 24,
12
+ alignItems: 'center',
13
+ },
14
+ avatar: {
15
+ width: 88,
16
+ height: 88,
17
+ borderRadius: 44,
18
+ backgroundColor: Colors.Primary,
19
+ },
20
+ loading: {
21
+ marginTop: 24,
22
+ },
23
+ row: {
24
+ flexDirection: 'row',
25
+ justifyContent: 'space-between',
26
+ paddingHorizontal: 16,
27
+ paddingVertical: 24,
28
+ },
29
+ rowRight: {
30
+ flexDirection: 'row',
31
+ },
32
+ icon: {
33
+ marginLeft: 8,
34
+ width: 15,
35
+ },
36
+ textName: {
37
+ paddingTop: 16,
38
+ },
39
+ role: {
40
+ flexDirection: 'row',
41
+ justifyContent: 'space-between',
42
+ alignItems: 'center',
43
+ padding: 10,
44
+ marginTop: 32,
45
+ marginHorizontal: 16,
46
+ borderRadius: 8,
47
+ shadowColor: Colors.Gray7,
48
+ backgroundColor: Colors.White,
49
+ shadowOffset: {
50
+ width: 0,
51
+ height: 3,
52
+ },
53
+ shadowOpacity: 0.22,
54
+ shadowRadius: 3,
55
+ elevation: 3,
56
+ },
57
+ leftRole: {
58
+ justifyContent: 'space-between',
59
+ },
60
+ arrowRight: {
61
+ position: 'absolute',
62
+ right: 10,
63
+ top: '55%',
64
+ transform: [{ rotate: '180deg' }],
65
+ tintColor: Colors.Gray7,
66
+ width: 7,
67
+ height: 11,
68
+ },
69
+ removeButton: {
70
+ position: 'absolute',
71
+ bottom: 0,
72
+ borderWidth: 0,
73
+ alignSelf: 'center',
74
+ paddingBottom: 16 + getBottomSpace(),
75
+ },
76
+ removeBorderBottom: {
77
+ borderBottomWidth: 1,
78
+ borderBottomColor: Colors.Red,
79
+ },
80
+ paddingLeft16: {
81
+ paddingLeft: 16,
82
+ },
83
+ wrapCheckBoxStyle: {
84
+ alignItems: 'flex-start',
85
+ width: 18,
86
+ height: 18,
87
+ paddingRight: 16,
88
+ },
89
+ removeButtonStyle: {
90
+ color: Colors.Red,
91
+ },
92
+ });
@@ -0,0 +1,121 @@
1
+ import React from 'react';
2
+ import { create, act } from 'react-test-renderer';
3
+ import { TouchableOpacity } from 'react-native';
4
+ import InfoMemberUnit from '../InfoMemberUnit';
5
+ import { HeaderCustom } from '../../../commons/Header';
6
+ import axios from 'axios';
7
+ import { SCProvider } from '../../../context';
8
+ import { mockSCStore } from '../../../context/mockStore';
9
+ import { AlertAction } from '../../../commons';
10
+ import API from '../../../configs/API';
11
+ import { TESTID } from '../../../configs/Constants';
12
+
13
+ jest.mock('../../../hooks/Common', () => {
14
+ return {
15
+ useIsOwnerOfUnit: () => ({ isOwner: true }),
16
+ };
17
+ });
18
+ const mockUseIsFocused = jest.fn();
19
+ const mockedNavigate = jest.fn();
20
+ jest.mock('react', () => {
21
+ return {
22
+ ...jest.requireActual('react'),
23
+ memo: (x) => x,
24
+ };
25
+ });
26
+
27
+ jest.mock('@react-navigation/native', () => {
28
+ return {
29
+ ...jest.requireActual('@react-navigation/native'),
30
+ useRoute: jest.fn(),
31
+ useNavigation: () => ({
32
+ goBack: jest.fn(),
33
+ navigate: mockedNavigate,
34
+ }),
35
+ useIsFocused: () => mockUseIsFocused,
36
+ };
37
+ });
38
+
39
+ jest.mock('axios');
40
+
41
+ const wrapComponent = (route) => (
42
+ <SCProvider initState={mockSCStore({})}>
43
+ <InfoMemberUnit route={route} />
44
+ </SCProvider>
45
+ );
46
+
47
+ describe('Test InfoMemberUnit', () => {
48
+ let tree;
49
+ let route;
50
+
51
+ beforeEach(() => {
52
+ axios.get.mockClear();
53
+ mockedNavigate.mockClear();
54
+ route = {
55
+ params: {
56
+ unit: {
57
+ id: 1,
58
+ name: 'unit',
59
+ },
60
+ member: {
61
+ id: 1,
62
+ name: 'user1',
63
+ share_id: 1,
64
+ },
65
+ },
66
+ };
67
+ });
68
+ it('render InfoMemberUnit', async () => {
69
+ mockUseIsFocused.mockImplementation(() => true);
70
+ const response = {
71
+ status: 200,
72
+ data: {
73
+ id: 1,
74
+ identity: 'owner',
75
+ phone_number: '88888',
76
+ name: 'user',
77
+ email: 'abc@gmail.com',
78
+ },
79
+ };
80
+ axios.get.mockImplementation(async () => {
81
+ return response;
82
+ });
83
+ await act(async () => {
84
+ tree = await create(wrapComponent(route));
85
+ });
86
+ const instance = tree.root;
87
+ const header = instance.findAllByType(HeaderCustom);
88
+ const alertAction = instance.findAllByType(AlertAction);
89
+ expect(header).toHaveLength(1);
90
+ expect(alertAction).toHaveLength(1);
91
+ await act(async () => {
92
+ alertAction[0].props.rightButtonClick();
93
+ });
94
+ });
95
+ it('render InfoMemberUnit delete member', async () => {
96
+ mockUseIsFocused.mockImplementation(() => true);
97
+ const response = {
98
+ status: 200,
99
+ };
100
+ axios.delete.mockImplementation(async () => {
101
+ return response;
102
+ });
103
+ await act(async () => {
104
+ tree = await create(wrapComponent(route));
105
+ });
106
+ const instance = tree.root;
107
+ const header = instance.findAllByType(HeaderCustom);
108
+ const buttonRemove = instance.findAll(
109
+ (el) =>
110
+ el.props.testID === TESTID.REMOVE_MEMBER && el.type === TouchableOpacity
111
+ );
112
+ expect(header).toHaveLength(1);
113
+ expect(buttonRemove).toHaveLength(1);
114
+ await act(async () => {
115
+ buttonRemove[0].props.onPress();
116
+ });
117
+ expect(axios.delete).toHaveBeenCalledWith(
118
+ API.SHARE.UNITS_MEMBER_DETAIL(1, undefined)
119
+ );
120
+ });
121
+ });
@@ -2,14 +2,11 @@
2
2
  import React from 'react';
3
3
  import { create, act } from 'react-test-renderer';
4
4
  import axios from 'axios';
5
- import Toast from 'react-native-toast-message';
6
5
  import MemberList from '../MemberList';
7
6
  import { AlertAction } from '../../../commons';
8
- import SharingMembers from '../../../commons/Sharing/MemberList';
9
7
  import API from '../../../configs/API';
10
8
  import { TESTID } from '../../../configs/Constants';
11
9
  import { TouchableOpacity } from 'react-native';
12
- import { getTranslate } from '../../../utils/I18n';
13
10
  import { SCProvider } from '../../../context';
14
11
  import { mockSCStore } from '../../../context/mockStore';
15
12
  import Routes from '../../../utils/Route';
@@ -17,6 +14,7 @@ import Routes from '../../../utils/Route';
17
14
  const mockedNavigate = jest.fn();
18
15
  const mockedDispatch = jest.fn();
19
16
  const mockedAddListener = jest.fn();
17
+ const mockUseIsFocused = jest.fn();
20
18
 
21
19
  jest.mock('@react-navigation/native', () => {
22
20
  return {
@@ -25,6 +23,9 @@ jest.mock('@react-navigation/native', () => {
25
23
  addListener: mockedAddListener,
26
24
  navigate: mockedNavigate,
27
25
  }),
26
+ useIsFocused: () => ({
27
+ isFocused: mockUseIsFocused,
28
+ }),
28
29
  };
29
30
  });
30
31
 
@@ -85,14 +86,6 @@ describe('test MemberList', () => {
85
86
  });
86
87
 
87
88
  test('AlertAction rightButtonClick', async () => {
88
- const response = {
89
- status: 200,
90
- data: '',
91
- };
92
- axios.delete.mockImplementation(async () => {
93
- return response;
94
- });
95
-
96
89
  let tree;
97
90
  await act(async () => {
98
91
  tree = await create(wrapComponent(route, localState));
@@ -102,17 +95,6 @@ describe('test MemberList', () => {
102
95
  await act(async () => {
103
96
  alertAction.props.rightButtonClick();
104
97
  });
105
- expect(axios.delete).toHaveBeenCalledWith(
106
- API.SHARE.UNITS_MEMBER_DETAIL(1, undefined)
107
- );
108
- expect(Toast.show).toHaveBeenCalledWith({
109
- type: 'success',
110
- position: 'bottom',
111
- text1: getTranslate('en', 'sharing_removed_user', {
112
- name: 'undefined',
113
- }),
114
- visibilityTime: 1000,
115
- });
116
98
  });
117
99
 
118
100
  test('get dataMembers', async () => {
@@ -122,6 +104,7 @@ describe('test MemberList', () => {
122
104
  {
123
105
  id: 1,
124
106
  name: 'a',
107
+ share_id: 1,
125
108
  },
126
109
  ],
127
110
  };
@@ -134,9 +117,11 @@ describe('test MemberList', () => {
134
117
  tree = await create(wrapComponent(route, localState));
135
118
  });
136
119
  const instance = tree.root;
137
- const sharingMember = instance.findAllByType(SharingMembers);
138
- expect(axios.get).toHaveBeenCalledWith(API.SHARE.UNITS_MEMBERS(1), {});
120
+ const sharingMember = instance.findAll(
121
+ (el) => el.props.testID === TESTID.SHARING_MEMBER
122
+ );
139
123
  expect(sharingMember).toHaveLength(1);
124
+ expect(axios.get).toHaveBeenCalledWith(API.SHARE.UNITS_MEMBERS(1), {});
140
125
  });
141
126
 
142
127
  test('WrapHeaderScrollable rightHeader', async () => {
@@ -9,6 +9,7 @@ import { ViewButtonBottom } from '../../../commons';
9
9
  import Routes from '../../../utils/Route';
10
10
  import { SCProvider } from '../../../context';
11
11
  import { mockSCStore } from '../../../context/mockStore';
12
+ import API from '../../../configs/API';
12
13
 
13
14
  jest.mock('axios');
14
15
 
@@ -194,4 +195,56 @@ describe('Test SelectPermission', () => {
194
195
  });
195
196
  expect(mockGoBack).toBeCalled();
196
197
  });
198
+ it('test selectPermission type share_device', () => {
199
+ mocSetdata();
200
+ const routes = {
201
+ params: {
202
+ unit: { id: 1 },
203
+ type: 'share_device',
204
+ member: { id: 1, name: 'a', phone_number: '89898888' },
205
+ },
206
+ };
207
+ const response = {
208
+ status: 200,
209
+ data: [
210
+ {
211
+ id: 204,
212
+ isChecked: true,
213
+ name: 'sensor',
214
+ sensors: [
215
+ {
216
+ actions: [{ id: 136, isChecked: true, name: 'action 1' }],
217
+ id: 123,
218
+ isChecked: true,
219
+ name: 'child1',
220
+ read_configs: [{ id: 137, isChecked: true, name: 'config 1' }],
221
+ },
222
+ ],
223
+ },
224
+ ],
225
+ };
226
+ axios.get.mockImplementation(async () => {
227
+ return response;
228
+ });
229
+ act(() => {
230
+ tree = create(wrapComponent(routes));
231
+ });
232
+ const instance = tree.root;
233
+ const ViewButtonBottomElement = instance.findAllByType(ViewButtonBottom);
234
+ expect(ViewButtonBottomElement).toHaveLength(1);
235
+ expect(axios.get).toHaveBeenCalledWith(
236
+ API.SHARE.UNIT_MEMBER_SHARE_DEVICE(1, 1),
237
+ {}
238
+ );
239
+ act(() => {
240
+ ViewButtonBottomElement[0].props.onRightClick();
241
+ });
242
+ expect(mockNavigate).toBeCalledWith(Routes.SharingInviteMembers, {
243
+ permissions: {
244
+ control_permissions: [{ id: 123, values: [136] }],
245
+ read_permissions: [{ id: 123, values: [137] }],
246
+ },
247
+ unit: 1,
248
+ });
249
+ });
197
250
  });