@eohjsc/react-native-smart-city 0.2.78 → 0.2.82
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.
- package/package.json +2 -2
- package/src/commons/Device/HorizontalBarChart.js +1 -1
- package/src/commons/Device/LinearChart.js +1 -1
- package/src/commons/ImagePicker/__test__/ImagePicker.test.js +24 -3
- package/src/commons/MediaPlayerDetail/index.js +1 -0
- package/src/commons/SubUnit/Favorites/index.js +7 -4
- package/src/commons/SubUnit/ShortDetail.js +7 -4
- package/src/configs/API.js +2 -1
- package/src/configs/Constants.js +3 -0
- package/src/screens/AddCommon/SelectSubUnit.js +6 -0
- package/src/screens/AddNewGateway/__test__/SetupGateway.test.js +52 -0
- package/src/screens/AllCamera/index.js +76 -44
- package/src/screens/Notification/components/NotificationItem.js +19 -1
- package/src/screens/Notification/index.js +4 -1
- package/src/screens/Sharing/SelectUser.js +17 -10
- package/src/screens/Sharing/__test__/SelectUser.test.js +73 -0
- package/src/screens/Unit/Detail.js +18 -4
- package/src/screens/Unit/ManageUnit.js +88 -32
- package/src/screens/Unit/ManageUnitStyles.js +20 -0
- package/src/screens/Unit/SmartAccount.js +6 -2
- package/src/screens/Unit/__test__/Detail.test.js +44 -1
- package/src/screens/Unit/__test__/ManageUnit.test.js +69 -0
- package/src/screens/Unit/__test__/SmartAccount.test.js +37 -8
- package/src/screens/Unit/hook/useStateAlertRemove.js +1 -1
- package/src/utils/Apis/axios.js +7 -2
- package/src/utils/I18n/translations/en.json +3 -1
- package/src/utils/I18n/translations/vi.json +3 -1
- package/src/{screens/Notification → utils}/Monitor.js +1 -1
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.2.
|
|
4
|
+
"version": "0.2.82",
|
|
5
5
|
"description": "TODO",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
@@ -97,10 +97,10 @@
|
|
|
97
97
|
"dependencies": {
|
|
98
98
|
"@ant-design/icons-react-native": "^2.2.1",
|
|
99
99
|
"@ant-design/react-native": "^4.0.5",
|
|
100
|
+
"@eohjsc/highcharts": "^1.0.3",
|
|
100
101
|
"@formatjs/intl-getcanonicallocales": "^1.4.5",
|
|
101
102
|
"@formatjs/intl-numberformat": "^5.6.2",
|
|
102
103
|
"@formatjs/intl-pluralrules": "^3.4.7",
|
|
103
|
-
"@highcharts/highcharts-react-native": "highcharts/highcharts-react-native#107/head",
|
|
104
104
|
"@invertase/react-native-apple-authentication": "^1.1.2",
|
|
105
105
|
"@messageformat/core": "^3.0.0",
|
|
106
106
|
"@react-native-community/async-storage": "^1.12.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { memo, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { View, StyleSheet } from 'react-native';
|
|
3
|
-
import HighchartsReactNative from '@
|
|
3
|
+
import HighchartsReactNative from '@eohjsc/highcharts';
|
|
4
4
|
import { isEmpty } from 'lodash';
|
|
5
5
|
|
|
6
6
|
import { Colors } from '../../configs';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { memo, useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import { StyleSheet, View } from 'react-native';
|
|
3
|
-
import HighchartsReactNative from '@
|
|
3
|
+
import HighchartsReactNative from '@eohjsc/highcharts';
|
|
4
4
|
import moment from 'moment';
|
|
5
5
|
import { Colors } from '../../configs';
|
|
6
6
|
|
|
@@ -41,9 +41,9 @@ describe('Test ImagePicker', () => {
|
|
|
41
41
|
Platform.OS = 'android';
|
|
42
42
|
const options = {
|
|
43
43
|
mediaType: 'photo',
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
compressImageMaxWidth: 1280,
|
|
45
|
+
compressImageMaxHeight: 720,
|
|
46
|
+
compressImageQuality: 0.8,
|
|
47
47
|
};
|
|
48
48
|
act(() => {
|
|
49
49
|
tree = renderer.create(wrapComponent(options));
|
|
@@ -51,5 +51,26 @@ describe('Test ImagePicker', () => {
|
|
|
51
51
|
const instance = tree.root;
|
|
52
52
|
const textInputs = instance.findAllByType(ButtonPopup);
|
|
53
53
|
expect(textInputs.length).toBe(1);
|
|
54
|
+
act(() => {
|
|
55
|
+
textInputs[0].props.onPressMain();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
test('create ImagePicker onChooseFile', () => {
|
|
59
|
+
Platform.OS = 'android';
|
|
60
|
+
const options = {
|
|
61
|
+
mediaType: 'photo',
|
|
62
|
+
compressImageMaxWidth: 1280,
|
|
63
|
+
compressImageMaxHeight: 720,
|
|
64
|
+
compressImageQuality: 0.8,
|
|
65
|
+
};
|
|
66
|
+
act(() => {
|
|
67
|
+
tree = renderer.create(wrapComponent(options));
|
|
68
|
+
});
|
|
69
|
+
const instance = tree.root;
|
|
70
|
+
const textInputs = instance.findAllByType(ButtonPopup);
|
|
71
|
+
expect(textInputs.length).toBe(1);
|
|
72
|
+
act(() => {
|
|
73
|
+
textInputs[0].props.onPressSecondary();
|
|
74
|
+
});
|
|
54
75
|
});
|
|
55
76
|
});
|
|
@@ -31,9 +31,12 @@ const SubUnitFavorites = ({
|
|
|
31
31
|
favorites.devices.forEach((sensor) => {
|
|
32
32
|
params.append('sensors', sensor.id);
|
|
33
33
|
});
|
|
34
|
-
const { success, data } = await axiosGet(
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
const { success, data } = await axiosGet(
|
|
35
|
+
API.UNIT.SENSORS_STATUS(unit.id),
|
|
36
|
+
{
|
|
37
|
+
params: params,
|
|
38
|
+
}
|
|
39
|
+
);
|
|
37
40
|
if (success) {
|
|
38
41
|
setSensorsStatus(data);
|
|
39
42
|
}
|
|
@@ -45,7 +48,7 @@ const SubUnitFavorites = ({
|
|
|
45
48
|
} else {
|
|
46
49
|
clearInterval(intervalSensorStatus.current);
|
|
47
50
|
}
|
|
48
|
-
}, [isFocused, favorites?.devices]);
|
|
51
|
+
}, [isFocused, favorites?.devices, unit.id]);
|
|
49
52
|
|
|
50
53
|
const handleOnAddNew = () => {
|
|
51
54
|
alert(t('feature_under_development'));
|
|
@@ -35,9 +35,12 @@ const ShortDetailSubUnit = ({ unit, station, isGGHomeConnected }) => {
|
|
|
35
35
|
station.sensors.forEach((sensor) => {
|
|
36
36
|
params.append('sensors', sensor.id);
|
|
37
37
|
});
|
|
38
|
-
const { success, data } = await axiosGet(
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const { success, data } = await axiosGet(
|
|
39
|
+
API.UNIT.SENSORS_STATUS(unit.id),
|
|
40
|
+
{
|
|
41
|
+
params: params,
|
|
42
|
+
}
|
|
43
|
+
);
|
|
41
44
|
if (success) {
|
|
42
45
|
setSensorsStatus(data);
|
|
43
46
|
}
|
|
@@ -49,7 +52,7 @@ const ShortDetailSubUnit = ({ unit, station, isGGHomeConnected }) => {
|
|
|
49
52
|
} else {
|
|
50
53
|
clearInterval(intervalSensorStatus.current);
|
|
51
54
|
}
|
|
52
|
-
}, [isFocused, station?.sensors]);
|
|
55
|
+
}, [isFocused, station?.sensors, unit.id]);
|
|
53
56
|
|
|
54
57
|
const renderCamera = () => {
|
|
55
58
|
if (station?.camera) {
|
package/src/configs/API.js
CHANGED
|
@@ -35,6 +35,8 @@ const API = {
|
|
|
35
35
|
SCConfig.apiRoot + `/property_manager/units/${id}/device_sensor/`,
|
|
36
36
|
ADD_GATEWAY: (id) =>
|
|
37
37
|
SCConfig.apiRoot + `/property_manager/units/${id}/add_gateway/`,
|
|
38
|
+
SENSORS_STATUS: (id) =>
|
|
39
|
+
SCConfig.apiRoot + `/property_manager/units/${id}/sensors_status/`,
|
|
38
40
|
},
|
|
39
41
|
SUB_UNIT: {
|
|
40
42
|
REMOVE_SUB_UNIT: (unitId, id) =>
|
|
@@ -87,7 +89,6 @@ const API = {
|
|
|
87
89
|
CHANGE_SUB_UNIT: (unit_id, station_id, id) =>
|
|
88
90
|
SCConfig.apiRoot +
|
|
89
91
|
`/property_manager/${unit_id}/sub_units/${station_id}/devices/${id}/change_sub_unit/`,
|
|
90
|
-
STATUS: () => SCConfig.apiRoot + '/property_manager/sensors/status/',
|
|
91
92
|
},
|
|
92
93
|
SHARED_SENSOR: {
|
|
93
94
|
ACCESS: (id) =>
|
package/src/configs/Constants.js
CHANGED
|
@@ -210,6 +210,7 @@ export const TESTID = {
|
|
|
210
210
|
SUB_UNIT_STATION: 'SUB_UNIT_STATION',
|
|
211
211
|
SUB_UNIT_SELECT_AUTOMATE_TYPE: 'SUB_UNIT_SELECT_AUTOMATE_TYPE',
|
|
212
212
|
SUB_UNIT_TEXT_DROPDOWN: 'SUB_UNIT_TEXT_DROPDOWN',
|
|
213
|
+
SUB_UNIT_GO_DETAIL: 'SUB_UNIT_GO_DETAIL',
|
|
213
214
|
|
|
214
215
|
// NavBar
|
|
215
216
|
NAVBAR_ICON_BARS: 'NAVBAR_ICON_BARS',
|
|
@@ -436,6 +437,7 @@ export const TESTID = {
|
|
|
436
437
|
MANAGE_UNIT_LIST_EMERGENCY_CONTACT: 'MANAGE_UNIT_LIST_EMERGENCY_CONTACT',
|
|
437
438
|
MANAGE_UNIT_DETAIL_LIST_CONTACT_BUTTON:
|
|
438
439
|
'MANAGE_UNIT_DETAIL_LIST_CONTACT_BUTTON',
|
|
440
|
+
MANAGE_UNIT_GO_TO_SUBUNIT: 'MANAGE_UNIT_GO_TO_SUBUNIT',
|
|
439
441
|
|
|
440
442
|
// Add Vehicle
|
|
441
443
|
ADD_VEHICLE_TAKE_PHOTO: 'ADD_VEHICLE_TAKE_PHOTO',
|
|
@@ -635,6 +637,7 @@ export const NOTIFICATION_TYPES = {
|
|
|
635
637
|
NOTIFY_REMOVE_UNIT: 'NOTIFY_REMOVE_UNIT',
|
|
636
638
|
NOTIFY_REMOVE_MEMBER: 'NOTIFY_REMOVE_MEMBER',
|
|
637
639
|
NOTIFY_MEMBER_LEAVE_UNIT: 'NOTIFY_MEMBER_LEAVE_UNIT',
|
|
640
|
+
NOTIFY_RENAME_UNIT: 'NOTIFY_RENAME_UNIT',
|
|
638
641
|
};
|
|
639
642
|
|
|
640
643
|
export const ACTIVITY_LOG_TYPES = {
|
|
@@ -24,6 +24,8 @@ const AddCommonSelectSubUnit = ({ route }) => {
|
|
|
24
24
|
listSelectDevice,
|
|
25
25
|
smart_account_id,
|
|
26
26
|
smart_account_id_from_backend,
|
|
27
|
+
username,
|
|
28
|
+
brand,
|
|
27
29
|
} = route.params;
|
|
28
30
|
const [selectedIndex, setSelectedIndex] = useState(-1);
|
|
29
31
|
const [unit, setUnit] = useState([]);
|
|
@@ -86,6 +88,8 @@ const AddCommonSelectSubUnit = ({ route }) => {
|
|
|
86
88
|
smart_account_id: smart_account_id,
|
|
87
89
|
unit_id: unit_id,
|
|
88
90
|
smart_account_id_from_backend: smart_account_id_from_backend,
|
|
91
|
+
username,
|
|
92
|
+
brand,
|
|
89
93
|
});
|
|
90
94
|
break;
|
|
91
95
|
case 'AddVconnexDevice':
|
|
@@ -115,6 +119,8 @@ const AddCommonSelectSubUnit = ({ route }) => {
|
|
|
115
119
|
smart_account_id,
|
|
116
120
|
unit_id,
|
|
117
121
|
smart_account_id_from_backend,
|
|
122
|
+
username,
|
|
123
|
+
brand,
|
|
118
124
|
]);
|
|
119
125
|
|
|
120
126
|
const handleSelectIndex = (index) => {
|
|
@@ -7,6 +7,9 @@ import { SCProvider } from '../../../context';
|
|
|
7
7
|
import { mockSCStore } from '../../../context/mockStore';
|
|
8
8
|
import SetupGatewayWifi from '../SetupGatewayWifi';
|
|
9
9
|
import DisplayChecking from '../../../commons/DisplayChecking';
|
|
10
|
+
import { AlertAction } from '../../../commons';
|
|
11
|
+
import _TextInputPassword from '../../../commons/Form/TextInputPassword';
|
|
12
|
+
import _TextInput from '../../../commons/Form/TextInput';
|
|
10
13
|
|
|
11
14
|
const wrapComponent = (route) => (
|
|
12
15
|
<SCProvider initState={mockSCStore({})}>
|
|
@@ -87,4 +90,53 @@ describe('Test SetupGatewayWifi', () => {
|
|
|
87
90
|
const displayLoadingConnect = instance.findByType(DisplayChecking);
|
|
88
91
|
expect(displayLoadingConnect.props.visible).toBeTruthy();
|
|
89
92
|
});
|
|
93
|
+
|
|
94
|
+
test('test render AlertAction', async () => {
|
|
95
|
+
await act(async () => {
|
|
96
|
+
tree = await create(wrapComponent(route));
|
|
97
|
+
});
|
|
98
|
+
const instance = tree.root;
|
|
99
|
+
const alertAction = instance.findByType(AlertAction);
|
|
100
|
+
await act(async () => {
|
|
101
|
+
alertAction.props.hideModal();
|
|
102
|
+
alertAction.props.leftButtonClick();
|
|
103
|
+
});
|
|
104
|
+
expect(alertAction).toBeDefined();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('test render _TextInputPassword', async () => {
|
|
108
|
+
await act(async () => {
|
|
109
|
+
tree = await create(wrapComponent(route));
|
|
110
|
+
});
|
|
111
|
+
const instance = tree.root;
|
|
112
|
+
const textInputPassword = instance.findByType(_TextInputPassword);
|
|
113
|
+
await act(async () => {
|
|
114
|
+
textInputPassword.props.onChange('new_name');
|
|
115
|
+
});
|
|
116
|
+
expect(textInputPassword.props.value).toEqual('new_name');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('test render TextInput', async () => {
|
|
120
|
+
await act(async () => {
|
|
121
|
+
tree = await create(wrapComponent(route));
|
|
122
|
+
});
|
|
123
|
+
const instance = tree.root;
|
|
124
|
+
const textInput = instance.findByType(_TextInput);
|
|
125
|
+
await act(async () => {
|
|
126
|
+
textInput.props.onChange('new_text');
|
|
127
|
+
});
|
|
128
|
+
expect(textInput.props.value).toEqual('new_text');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('test render DisplayChecking', async () => {
|
|
132
|
+
await act(async () => {
|
|
133
|
+
tree = await create(wrapComponent(route));
|
|
134
|
+
});
|
|
135
|
+
const instance = tree.root;
|
|
136
|
+
const displayChecking = instance.findByType(DisplayChecking);
|
|
137
|
+
await act(async () => {
|
|
138
|
+
displayChecking.props.onClose();
|
|
139
|
+
});
|
|
140
|
+
expect(displayChecking).toBeDefined();
|
|
141
|
+
});
|
|
90
142
|
});
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react';
|
|
2
8
|
import { View, Text, TouchableOpacity, Platform, Image } from 'react-native';
|
|
3
9
|
import { useRoute, useNavigation } from '@react-navigation/native';
|
|
4
10
|
import { chunk } from 'lodash';
|
|
@@ -28,36 +34,51 @@ const AllCamera = () => {
|
|
|
28
34
|
const [isFullScreen, setIsFullScreen] = useState(false);
|
|
29
35
|
const [dataFullScreen, setDataFullScreen] = useState();
|
|
30
36
|
|
|
31
|
-
const handleFullScreen = (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
const handleFullScreen = useCallback(
|
|
38
|
+
(data) => {
|
|
39
|
+
setIsFullScreen(!isFullScreen);
|
|
40
|
+
setDataFullScreen(data);
|
|
41
|
+
},
|
|
42
|
+
[isFullScreen]
|
|
43
|
+
);
|
|
35
44
|
|
|
36
|
-
const onPressAmount = (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
const onPressAmount = useCallback(
|
|
46
|
+
(value) => () => {
|
|
47
|
+
setAmount(value);
|
|
48
|
+
value === 1 && setActiveCamera(arrCameras[0]);
|
|
49
|
+
const dataTemp = [...arrCameras];
|
|
50
|
+
setData(value === 1 ? dataTemp : chunk(dataTemp, value));
|
|
51
|
+
setCurrentPage(1);
|
|
52
|
+
},
|
|
53
|
+
[arrCameras]
|
|
54
|
+
);
|
|
42
55
|
|
|
43
|
-
const onPressNext = () =>
|
|
56
|
+
const onPressNext = useCallback(() => {
|
|
44
57
|
currentPage !== data.length && carouselRef?.current?.snapToNext();
|
|
58
|
+
}, [currentPage, data.length]);
|
|
45
59
|
|
|
46
|
-
const onPressPrev = () =>
|
|
60
|
+
const onPressPrev = useCallback(() => {
|
|
47
61
|
currentPage !== 1 && carouselRef?.current?.snapToPrev();
|
|
62
|
+
}, [currentPage]);
|
|
48
63
|
|
|
49
|
-
const onSnapToItem = (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
64
|
+
const onSnapToItem = useCallback(
|
|
65
|
+
(index) => {
|
|
66
|
+
setActiveCamera(data[index]);
|
|
67
|
+
setCurrentPage(index + 1);
|
|
68
|
+
},
|
|
69
|
+
[data]
|
|
70
|
+
);
|
|
53
71
|
|
|
54
72
|
const onClose = () => {
|
|
55
73
|
setIsFullScreen(false);
|
|
56
74
|
};
|
|
57
75
|
|
|
58
|
-
const goToPlayBack = (
|
|
59
|
-
|
|
60
|
-
|
|
76
|
+
const goToPlayBack = useCallback(
|
|
77
|
+
(item, thumbnail) => () => {
|
|
78
|
+
navigate(Routes.PlaybackCamera, { item, thumbnail });
|
|
79
|
+
},
|
|
80
|
+
[navigate]
|
|
81
|
+
);
|
|
61
82
|
|
|
62
83
|
const CameraItem = ({ item, width, height }) => {
|
|
63
84
|
return (
|
|
@@ -82,7 +103,7 @@ const AllCamera = () => {
|
|
|
82
103
|
);
|
|
83
104
|
};
|
|
84
105
|
|
|
85
|
-
const renderItem = ({ item
|
|
106
|
+
const renderItem = ({ item }) => {
|
|
86
107
|
return (
|
|
87
108
|
<View style={styles.wrapItem}>
|
|
88
109
|
{amount === 1 ? (
|
|
@@ -93,13 +114,13 @@ const AllCamera = () => {
|
|
|
93
114
|
<CameraItem
|
|
94
115
|
key={item?.id}
|
|
95
116
|
item={item[0]}
|
|
96
|
-
width={
|
|
117
|
+
width={Constants.width / 2}
|
|
97
118
|
height={112}
|
|
98
119
|
/>
|
|
99
120
|
<CameraItem
|
|
100
121
|
key={item?.id}
|
|
101
122
|
item={item[1]}
|
|
102
|
-
width={
|
|
123
|
+
width={Constants.width / 2}
|
|
103
124
|
height={112}
|
|
104
125
|
/>
|
|
105
126
|
</View>
|
|
@@ -107,13 +128,13 @@ const AllCamera = () => {
|
|
|
107
128
|
<CameraItem
|
|
108
129
|
key={item?.id}
|
|
109
130
|
item={item[2]}
|
|
110
|
-
width={
|
|
131
|
+
width={Constants.width / 2}
|
|
111
132
|
height={112}
|
|
112
133
|
/>
|
|
113
134
|
<CameraItem
|
|
114
135
|
key={item?.id}
|
|
115
136
|
item={item[3]}
|
|
116
|
-
width={
|
|
137
|
+
width={Constants.width / 2}
|
|
117
138
|
height={112}
|
|
118
139
|
/>
|
|
119
140
|
</View>
|
|
@@ -138,19 +159,23 @@ const AllCamera = () => {
|
|
|
138
159
|
|
|
139
160
|
const renderCarousel = useMemo(() => {
|
|
140
161
|
return (
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
162
|
+
<View style={styles.wrapCarousel}>
|
|
163
|
+
<Carousel
|
|
164
|
+
ref={carouselRef}
|
|
165
|
+
data={data}
|
|
166
|
+
renderItem={renderItem}
|
|
167
|
+
sliderWidth={Constants.width}
|
|
168
|
+
itemWidth={Constants.width}
|
|
169
|
+
inactiveSlideScale={1}
|
|
170
|
+
onSnapToItem={onSnapToItem}
|
|
171
|
+
inactiveSlideOpacity={1}
|
|
172
|
+
extraData={data}
|
|
173
|
+
loop={false}
|
|
174
|
+
lockScrollWhileSnapping
|
|
175
|
+
isForceIndex={Platform.OS === 'android'}
|
|
176
|
+
removeClippedSubviews={false}
|
|
177
|
+
/>
|
|
178
|
+
</View>
|
|
154
179
|
);
|
|
155
180
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
156
181
|
}, [data]);
|
|
@@ -182,6 +207,17 @@ const AllCamera = () => {
|
|
|
182
207
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
183
208
|
}, [amount]);
|
|
184
209
|
|
|
210
|
+
const renderName = useMemo(
|
|
211
|
+
() => (
|
|
212
|
+
<Text style={styles.cameraName}>
|
|
213
|
+
{amount === 1
|
|
214
|
+
? activeCamera?.configuration?.name
|
|
215
|
+
: `${currentPage}/${data.length}`}
|
|
216
|
+
</Text>
|
|
217
|
+
),
|
|
218
|
+
[activeCamera?.configuration?.name, amount, currentPage, data.length]
|
|
219
|
+
);
|
|
220
|
+
|
|
185
221
|
useEffect(() => {
|
|
186
222
|
const to = setTimeout(() => {
|
|
187
223
|
carouselRef?.current?.snapToItem();
|
|
@@ -203,16 +239,12 @@ const AllCamera = () => {
|
|
|
203
239
|
<TouchableOpacity style={styles.buttonNext} onPress={onPressPrev}>
|
|
204
240
|
<Image source={Images.arrowLeft} />
|
|
205
241
|
</TouchableOpacity>
|
|
206
|
-
|
|
207
|
-
{amount === 1
|
|
208
|
-
? activeCamera?.configuration?.name
|
|
209
|
-
: `${currentPage}/${data.length}`}
|
|
210
|
-
</Text>
|
|
242
|
+
{renderName}
|
|
211
243
|
<TouchableOpacity style={styles.buttonNext} onPress={onPressNext}>
|
|
212
244
|
<Image source={Images.arrowLeft} style={styles.arrowRight} />
|
|
213
245
|
</TouchableOpacity>
|
|
214
246
|
</View>
|
|
215
|
-
|
|
247
|
+
{renderCarousel}
|
|
216
248
|
{renderAmount}
|
|
217
249
|
</View>
|
|
218
250
|
</View>
|
|
@@ -48,9 +48,9 @@ const NotificationItem = memo(({ item }) => {
|
|
|
48
48
|
const renderItem = useCallback(() => {
|
|
49
49
|
const paramsJSON = JSON.parse(params.replace(regex, '"'));
|
|
50
50
|
const booking_id = paramsJSON.booking_id && paramsJSON.booking_id;
|
|
51
|
+
const unitId = paramsJSON?.unit_id;
|
|
51
52
|
switch (content_code) {
|
|
52
53
|
case NOTIFICATION_TYPES.NOTIFY_INVITE_MEMBER:
|
|
53
|
-
const unitId = paramsJSON?.unit_id;
|
|
54
54
|
return {
|
|
55
55
|
content: customColorText(
|
|
56
56
|
t('text_notification_content_invite_member'),
|
|
@@ -254,6 +254,24 @@ const NotificationItem = memo(({ item }) => {
|
|
|
254
254
|
<IconComponent icon={'home'} style={styles.backgroundSummer} />
|
|
255
255
|
),
|
|
256
256
|
};
|
|
257
|
+
case NOTIFICATION_TYPES.NOTIFY_RENAME_UNIT:
|
|
258
|
+
return {
|
|
259
|
+
content: customColorText(
|
|
260
|
+
t('text_notification_content_rename_unit'),
|
|
261
|
+
arrParams
|
|
262
|
+
),
|
|
263
|
+
redirect: () => {
|
|
264
|
+
navigation.navigate(Routes.UnitStack, {
|
|
265
|
+
screen: Routes.UnitDetail,
|
|
266
|
+
params: {
|
|
267
|
+
unitId,
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
iconContent: (
|
|
272
|
+
<IconComponent icon={'home'} style={styles.backgroundSummer} />
|
|
273
|
+
),
|
|
274
|
+
};
|
|
257
275
|
default:
|
|
258
276
|
return {
|
|
259
277
|
content: customColorText(t('this_notification_will_be_updated_soon')),
|
|
@@ -8,7 +8,10 @@ import { axiosGet } from '../../utils/Apis/axios';
|
|
|
8
8
|
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
9
9
|
import NotificationItem from './components/NotificationItem';
|
|
10
10
|
import WrapHeaderScrollable from '../../commons/Sharing/WrapHeaderScrollable';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
watchNotificationData,
|
|
13
|
+
unwatchNotificationData,
|
|
14
|
+
} from '../../utils/Monitor';
|
|
12
15
|
import { useSCContextSelector } from '../../context';
|
|
13
16
|
|
|
14
17
|
let page = 1;
|
|
@@ -48,16 +48,23 @@ const SelectUser = ({ route }) => {
|
|
|
48
48
|
if (success) {
|
|
49
49
|
setUsers([...users, data.user]);
|
|
50
50
|
} else {
|
|
51
|
-
|
|
52
|
-
data
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
if (data.non_field_errors) {
|
|
52
|
+
ToastBottomHelper.error(data.non_field_errors);
|
|
53
|
+
} else {
|
|
54
|
+
const textTemp = t(
|
|
55
|
+
data?.phone
|
|
56
|
+
? 'text_phone_share_permission'
|
|
57
|
+
: data?.email
|
|
58
|
+
? 'text_email_share_permission'
|
|
59
|
+
: ''
|
|
60
|
+
);
|
|
61
|
+
ToastBottomHelper.error(
|
|
62
|
+
t('error_share_permission', {
|
|
63
|
+
data: phone || email,
|
|
64
|
+
text: textTemp,
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
}
|
|
61
68
|
}
|
|
62
69
|
},
|
|
63
70
|
[users, unit.id, permissions, t]
|
|
@@ -11,6 +11,7 @@ import { API } from '../../../configs';
|
|
|
11
11
|
import { getTranslate } from '../../../utils/I18n';
|
|
12
12
|
import { SCProvider } from '../../../context';
|
|
13
13
|
import { mockSCStore } from '../../../context/mockStore';
|
|
14
|
+
import { ToastBottomHelper } from '../../../utils/Utils';
|
|
14
15
|
|
|
15
16
|
const wrapComponent = (route) => (
|
|
16
17
|
<SCProvider initState={mockSCStore({})}>
|
|
@@ -191,4 +192,76 @@ describe('test SelectUser container', () => {
|
|
|
191
192
|
accountList = instance.findAllByType(AccountList);
|
|
192
193
|
expect(accountList).toHaveLength(0);
|
|
193
194
|
});
|
|
195
|
+
|
|
196
|
+
test('_TextInput onChange unitOwner phone, validate and call api sharedPermission', async () => {
|
|
197
|
+
const response = {
|
|
198
|
+
status: 400,
|
|
199
|
+
data: { non_field_errors: 'You cannot invite yourself' },
|
|
200
|
+
};
|
|
201
|
+
mockAxiosPost(response);
|
|
202
|
+
|
|
203
|
+
const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
|
|
204
|
+
|
|
205
|
+
await act(async () => {
|
|
206
|
+
tree = create(wrapComponent(route));
|
|
207
|
+
});
|
|
208
|
+
const instance = tree.root;
|
|
209
|
+
|
|
210
|
+
const textInput = instance.findByType(_TextInput);
|
|
211
|
+
const button = instance.findByType(Button);
|
|
212
|
+
|
|
213
|
+
await act(async () => {
|
|
214
|
+
await textInput.props.onChange('0909123456');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
await act(async () => {
|
|
218
|
+
await button.props.onPress();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
expect(textInput.props.errorText).toEqual('');
|
|
222
|
+
expect(axios.post).toHaveBeenCalledWith(API.SHARE.SHARE(), {
|
|
223
|
+
phone: '0909123456',
|
|
224
|
+
email: '',
|
|
225
|
+
unit: 1,
|
|
226
|
+
permissions: { controlPermissions: {}, readPermissions: {} },
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
expect(spyToastError).toBeCalled();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('_TextInput onChange phone, validate and call api not sharedPermission', async () => {
|
|
233
|
+
const response = {
|
|
234
|
+
status: 400,
|
|
235
|
+
data: { phone: '0909123456' },
|
|
236
|
+
};
|
|
237
|
+
mockAxiosPost(response);
|
|
238
|
+
|
|
239
|
+
const spyToastError = jest.spyOn(ToastBottomHelper, 'error');
|
|
240
|
+
|
|
241
|
+
await act(async () => {
|
|
242
|
+
tree = create(wrapComponent(route));
|
|
243
|
+
});
|
|
244
|
+
const instance = tree.root;
|
|
245
|
+
|
|
246
|
+
const textInput = instance.findByType(_TextInput);
|
|
247
|
+
const button = instance.findByType(Button);
|
|
248
|
+
|
|
249
|
+
await act(async () => {
|
|
250
|
+
await textInput.props.onChange('0909123456');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
await act(async () => {
|
|
254
|
+
await button.props.onPress();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
expect(textInput.props.errorText).toEqual('');
|
|
258
|
+
expect(axios.post).toHaveBeenCalledWith(API.SHARE.SHARE(), {
|
|
259
|
+
phone: '0909123456',
|
|
260
|
+
email: '',
|
|
261
|
+
unit: 1,
|
|
262
|
+
permissions: { controlPermissions: {}, readPermissions: {} },
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
expect(spyToastError).toBeCalled();
|
|
266
|
+
});
|
|
194
267
|
});
|
|
@@ -26,7 +26,11 @@ import { useNavigation } from '@react-navigation/native';
|
|
|
26
26
|
import Routes from '../../utils/Route';
|
|
27
27
|
import SubUnitAutomate from '../../commons/SubUnit/OneTap';
|
|
28
28
|
import SubUnitFavorites from '../../commons/SubUnit/Favorites';
|
|
29
|
-
import { AUTOMATE_TYPE } from '../../configs/Constants';
|
|
29
|
+
import { AUTOMATE_TYPE, NOTIFICATION_TYPES } from '../../configs/Constants';
|
|
30
|
+
import {
|
|
31
|
+
watchNotificationData,
|
|
32
|
+
unwatchNotificationData,
|
|
33
|
+
} from '../../utils/Monitor';
|
|
30
34
|
|
|
31
35
|
const UnitDetail = ({ route }) => {
|
|
32
36
|
const t = useTranslations();
|
|
@@ -43,6 +47,7 @@ const UnitDetail = ({ route }) => {
|
|
|
43
47
|
const isFocused = useIsFocused();
|
|
44
48
|
const { stateData, setAction } = useContext(SCContext);
|
|
45
49
|
const { navigate } = useNavigation();
|
|
50
|
+
const user = useSCContextSelector((state) => state?.auth?.account?.user);
|
|
46
51
|
const isLavidaSource = useSCContextSelector(
|
|
47
52
|
(state) => state.app.isLavidaSource
|
|
48
53
|
);
|
|
@@ -144,9 +149,13 @@ const UnitDetail = ({ route }) => {
|
|
|
144
149
|
);
|
|
145
150
|
}, [t, unitId]);
|
|
146
151
|
|
|
147
|
-
const onRefresh = useCallback(
|
|
148
|
-
|
|
149
|
-
|
|
152
|
+
const onRefresh = useCallback(
|
|
153
|
+
(data = { content_code: NOTIFICATION_TYPES.NOTIFY_RENAME_UNIT }) => {
|
|
154
|
+
data.content_code === NOTIFICATION_TYPES.NOTIFY_RENAME_UNIT &&
|
|
155
|
+
fetchDetails();
|
|
156
|
+
},
|
|
157
|
+
[fetchDetails]
|
|
158
|
+
);
|
|
150
159
|
|
|
151
160
|
const handleAppStateChange = useCallback(
|
|
152
161
|
(nextAppState) => {
|
|
@@ -312,6 +321,11 @@ const UnitDetail = ({ route }) => {
|
|
|
312
321
|
navigate(isLavidaSource ? Routes.SmartHomeDashboard : Routes.Dashboard);
|
|
313
322
|
}, [isLavidaSource, navigate]);
|
|
314
323
|
|
|
324
|
+
useEffect(() => {
|
|
325
|
+
watchNotificationData(user, onRefresh);
|
|
326
|
+
return () => unwatchNotificationData(user);
|
|
327
|
+
}, [user, onRefresh]);
|
|
328
|
+
|
|
315
329
|
return (
|
|
316
330
|
<WrapParallaxScrollView
|
|
317
331
|
uriImg={unit.background}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useCallback, useEffect } from 'react';
|
|
1
|
+
import React, { useState, useCallback, useEffect, useMemo } from 'react';
|
|
2
2
|
import { View, TouchableOpacity, Image, Platform } from 'react-native';
|
|
3
3
|
import Animated from 'react-native-reanimated';
|
|
4
4
|
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
@@ -21,6 +21,7 @@ import { IconOutline } from '@ant-design/icons-react-native';
|
|
|
21
21
|
import styles from './ManageUnitStyles';
|
|
22
22
|
import { useNavigation } from '@react-navigation/native';
|
|
23
23
|
import { ModalCustom } from '../../commons/Modal';
|
|
24
|
+
import { Icon } from '@ant-design/react-native';
|
|
24
25
|
|
|
25
26
|
const ButtonWrapper = ({
|
|
26
27
|
onPress,
|
|
@@ -29,17 +30,26 @@ const ButtonWrapper = ({
|
|
|
29
30
|
value,
|
|
30
31
|
valueColor,
|
|
31
32
|
children,
|
|
33
|
+
icon,
|
|
32
34
|
}) => {
|
|
33
35
|
return (
|
|
34
36
|
<TouchableOpacity
|
|
35
37
|
onPress={onPress}
|
|
36
38
|
testID={testId}
|
|
37
|
-
style={styles.buttonWrapper}
|
|
39
|
+
style={!icon ? styles.buttonWrapper : styles.buttonWrapperAvatar}
|
|
38
40
|
>
|
|
39
41
|
<View style={styles.buttonInfo}>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
{!icon ? (
|
|
43
|
+
<Text type="H4" semibold>
|
|
44
|
+
{title}
|
|
45
|
+
</Text>
|
|
46
|
+
) : (
|
|
47
|
+
(icon && <Image source={{ uri: icon }} style={styles.avatar} />) || (
|
|
48
|
+
<View style={styles.avatar}>
|
|
49
|
+
<Icon name={'user'} size={27} />
|
|
50
|
+
</View>
|
|
51
|
+
)
|
|
52
|
+
)}
|
|
43
53
|
<View style={styles.buttonValue}>
|
|
44
54
|
<Text
|
|
45
55
|
type="Body"
|
|
@@ -66,15 +76,29 @@ const ManageUnit = ({ route }) => {
|
|
|
66
76
|
name: unit.name,
|
|
67
77
|
address: unit.address,
|
|
68
78
|
background: unit.background,
|
|
79
|
+
icon: unit.icon,
|
|
69
80
|
});
|
|
70
81
|
|
|
71
82
|
const [unitName, setUnitName] = useState(unit.name);
|
|
72
|
-
const [
|
|
83
|
+
const [imageUrlBackground, setImageUrlBackground] = useState('');
|
|
84
|
+
const [imageUrlIcon, setImageUrlIcon] = useState('');
|
|
85
|
+
const [checkSelectImage, setCheckSelectImage] = useState('');
|
|
73
86
|
const [showImagePicker, setShowImagePicker] = useState(false);
|
|
74
87
|
|
|
88
|
+
const onUpdateImage = useCallback(
|
|
89
|
+
async (data) => {
|
|
90
|
+
if (checkSelectImage === 'background') {
|
|
91
|
+
setImageUrlBackground(data);
|
|
92
|
+
} else {
|
|
93
|
+
setImageUrlIcon(data);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
[checkSelectImage]
|
|
97
|
+
);
|
|
98
|
+
|
|
75
99
|
const updateUnit = useCallback(
|
|
76
|
-
async (bodyData, headers) => {
|
|
77
|
-
const formData = createFormData(bodyData, [
|
|
100
|
+
async (bodyData, headers, dataInput) => {
|
|
101
|
+
const formData = createFormData(bodyData, [dataInput]);
|
|
78
102
|
|
|
79
103
|
const { success, data } = await axiosPatch(
|
|
80
104
|
API.UNIT.MANAGE_UNIT(unit.id),
|
|
@@ -110,20 +134,37 @@ const ManageUnit = ({ route }) => {
|
|
|
110
134
|
setHideEdit(true);
|
|
111
135
|
}, [unitName, setHideEdit, updateUnit]);
|
|
112
136
|
|
|
113
|
-
const handleChoosePhoto = useCallback(
|
|
114
|
-
|
|
115
|
-
|
|
137
|
+
const handleChoosePhoto = useCallback(
|
|
138
|
+
(text) => {
|
|
139
|
+
setCheckSelectImage(text);
|
|
140
|
+
setShowImagePicker(true);
|
|
141
|
+
},
|
|
142
|
+
[setShowImagePicker]
|
|
143
|
+
);
|
|
116
144
|
|
|
117
145
|
useEffect(() => {
|
|
118
|
-
if (
|
|
146
|
+
if (imageUrlBackground) {
|
|
119
147
|
updateUnit(
|
|
120
|
-
{ background:
|
|
148
|
+
{ background: imageUrlBackground },
|
|
121
149
|
{
|
|
122
150
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
123
|
-
}
|
|
151
|
+
},
|
|
152
|
+
'background'
|
|
124
153
|
);
|
|
125
154
|
}
|
|
126
|
-
}, [
|
|
155
|
+
}, [imageUrlBackground, updateUnit]);
|
|
156
|
+
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (imageUrlIcon) {
|
|
159
|
+
updateUnit(
|
|
160
|
+
{ icon: imageUrlIcon },
|
|
161
|
+
{
|
|
162
|
+
headers: { 'Content-Type': 'multipart/form-data' },
|
|
163
|
+
},
|
|
164
|
+
'icon'
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}, [imageUrlIcon, updateUnit]);
|
|
127
168
|
|
|
128
169
|
const [showRemove, setshowRemove, setHideRemove] = useBoolean();
|
|
129
170
|
const goRemove = useCallback(async () => {
|
|
@@ -160,6 +201,12 @@ const ManageUnit = ({ route }) => {
|
|
|
160
201
|
compressImageQuality: 0.8,
|
|
161
202
|
};
|
|
162
203
|
|
|
204
|
+
const SensorNumbers = useMemo(() => {
|
|
205
|
+
const stations = unit?.stations || [];
|
|
206
|
+
return stations.filter((item) => !item.isFavorites && !item.isOneTap)
|
|
207
|
+
.length;
|
|
208
|
+
}, [unit.stations]);
|
|
209
|
+
|
|
163
210
|
return (
|
|
164
211
|
<>
|
|
165
212
|
<WrapHeaderScrollable
|
|
@@ -169,6 +216,12 @@ const ManageUnit = ({ route }) => {
|
|
|
169
216
|
<View style={styles.wraper}>
|
|
170
217
|
{isOwner && (
|
|
171
218
|
<>
|
|
219
|
+
<ButtonWrapper
|
|
220
|
+
onPress={() => handleChoosePhoto('avatar')}
|
|
221
|
+
value={t('icon_unit')}
|
|
222
|
+
icon={unitData.icon}
|
|
223
|
+
testID={TESTID.MANAGE_UNIT_CHANGE_PHOTO}
|
|
224
|
+
/>
|
|
172
225
|
<ButtonWrapper
|
|
173
226
|
onPress={setshowEdit}
|
|
174
227
|
testID={TESTID.MANAGE_UNIT_CHANGE_NAME}
|
|
@@ -187,10 +240,11 @@ const ManageUnit = ({ route }) => {
|
|
|
187
240
|
<ButtonWrapper
|
|
188
241
|
onPress={goToManageSubUnit}
|
|
189
242
|
title={t('manage_sub_units')}
|
|
190
|
-
value={`${
|
|
243
|
+
value={`${SensorNumbers} sub-units`}
|
|
244
|
+
testID={TESTID.MANAGE_UNIT_GO_TO_SUBUNIT}
|
|
191
245
|
/>
|
|
192
246
|
<ButtonWrapper
|
|
193
|
-
onPress={handleChoosePhoto}
|
|
247
|
+
onPress={() => handleChoosePhoto('background')}
|
|
194
248
|
title={t('unit_wallpaper')}
|
|
195
249
|
value={t('tap_to_change')}
|
|
196
250
|
valueColor={Colors.Orange}
|
|
@@ -213,7 +267,7 @@ const ManageUnit = ({ route }) => {
|
|
|
213
267
|
<ImagePicker
|
|
214
268
|
showImagePicker={showImagePicker}
|
|
215
269
|
setShowImagePicker={setShowImagePicker}
|
|
216
|
-
setImageUrl={
|
|
270
|
+
setImageUrl={onUpdateImage}
|
|
217
271
|
optionsCapture={options}
|
|
218
272
|
testID={TESTID.MANAGE_UNIT_IMAGE_PICKER}
|
|
219
273
|
/>
|
|
@@ -221,20 +275,22 @@ const ManageUnit = ({ route }) => {
|
|
|
221
275
|
)}
|
|
222
276
|
</View>
|
|
223
277
|
</WrapHeaderScrollable>
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
<Text
|
|
230
|
-
type={'H4'}
|
|
231
|
-
semibold
|
|
232
|
-
color={Colors.Red}
|
|
233
|
-
style={styles.removeBorderBottom}
|
|
278
|
+
{!showEdit && (
|
|
279
|
+
<TouchableOpacity
|
|
280
|
+
style={styles.removeButton}
|
|
281
|
+
onPress={setshowRemove}
|
|
282
|
+
testID={TESTID.MANAGE_UNIT_SHOW_REMOVE}
|
|
234
283
|
>
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
284
|
+
<Text
|
|
285
|
+
type={'H4'}
|
|
286
|
+
semibold
|
|
287
|
+
color={Colors.Red}
|
|
288
|
+
style={styles.removeBorderBottom}
|
|
289
|
+
>
|
|
290
|
+
{t('remove_unit')}
|
|
291
|
+
</Text>
|
|
292
|
+
</TouchableOpacity>
|
|
293
|
+
)}
|
|
238
294
|
<ModalCustom
|
|
239
295
|
isVisible={showEdit}
|
|
240
296
|
onBackButtonPress={setHideEdit}
|
|
@@ -250,7 +306,7 @@ const ManageUnit = ({ route }) => {
|
|
|
250
306
|
</Text>
|
|
251
307
|
</View>
|
|
252
308
|
<_TextInput
|
|
253
|
-
defaultValue={
|
|
309
|
+
defaultValue={unitData?.name}
|
|
254
310
|
onChange={(value) => setUnitName(value)}
|
|
255
311
|
textInputStyle={styles.textInputStyle}
|
|
256
312
|
wrapStyle={styles.textInputWrapStyle}
|
|
@@ -16,6 +16,12 @@ export default StyleSheet.create({
|
|
|
16
16
|
borderBottomWidth: 0.5,
|
|
17
17
|
borderBottomColor: Colors.Gray6,
|
|
18
18
|
},
|
|
19
|
+
buttonWrapperAvatar: {
|
|
20
|
+
paddingTop: 14,
|
|
21
|
+
paddingBottom: 24,
|
|
22
|
+
borderBottomWidth: 0.5,
|
|
23
|
+
borderBottomColor: Colors.Gray7,
|
|
24
|
+
},
|
|
19
25
|
buttonInfo: {
|
|
20
26
|
flex: 1,
|
|
21
27
|
flexDirection: 'row',
|
|
@@ -29,6 +35,7 @@ export default StyleSheet.create({
|
|
|
29
35
|
},
|
|
30
36
|
value: {
|
|
31
37
|
marginRight: 20,
|
|
38
|
+
fontSize: 14,
|
|
32
39
|
},
|
|
33
40
|
location: {
|
|
34
41
|
marginTop: 16,
|
|
@@ -104,4 +111,17 @@ export default StyleSheet.create({
|
|
|
104
111
|
textInputWrapStyle: {
|
|
105
112
|
marginTop: 0,
|
|
106
113
|
},
|
|
114
|
+
avatar: {
|
|
115
|
+
height: 50,
|
|
116
|
+
width: 50,
|
|
117
|
+
borderRadius: 25,
|
|
118
|
+
borderWidth: 0.5,
|
|
119
|
+
borderColor: Colors.Gray5,
|
|
120
|
+
justifyContent: 'center',
|
|
121
|
+
alignItems: 'center',
|
|
122
|
+
},
|
|
123
|
+
wrapAvatar: {
|
|
124
|
+
flexDirection: 'row',
|
|
125
|
+
justifyContent: 'space-between',
|
|
126
|
+
},
|
|
107
127
|
});
|
|
@@ -18,6 +18,7 @@ import { usePopover, useBoolean } from '../../hooks/Common';
|
|
|
18
18
|
import { MenuActionMore, AlertAction } from '../../commons';
|
|
19
19
|
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
20
20
|
import { useStateAlertRemove } from '../Unit/hook/useStateAlertRemove';
|
|
21
|
+
import { ToastBottomHelper } from '../../utils/Utils';
|
|
21
22
|
|
|
22
23
|
const ListSmartAccount = ({ route }) => {
|
|
23
24
|
const { unitId } = route?.params || {};
|
|
@@ -76,8 +77,11 @@ const ListSmartAccount = ({ route }) => {
|
|
|
76
77
|
const { success } = await axiosDelete(
|
|
77
78
|
API.SMART_ACCOUNT.REMOVE_SMART_ACCOUNT(id)
|
|
78
79
|
);
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
if (success) {
|
|
81
|
+
ToastBottomHelper.success(t('removed_successfully'));
|
|
82
|
+
getAllSmartAccounts();
|
|
83
|
+
}
|
|
84
|
+
}, [getAllSmartAccounts, hideAlertAction, t]);
|
|
81
85
|
|
|
82
86
|
const listMenuItem = useMemo(() => {
|
|
83
87
|
return [{ action: 'remove', text: t('remove_account') }];
|
|
@@ -74,7 +74,7 @@ describe('Test UnitDetail', () => {
|
|
|
74
74
|
|
|
75
75
|
const detailUnitApiUrl = API.UNIT.UNIT_DETAIL(1);
|
|
76
76
|
const summaryUnitApiUrl = API.UNIT.UNIT_SUMMARY(1);
|
|
77
|
-
const sensorStatusApiUrl = API.
|
|
77
|
+
const sensorStatusApiUrl = API.UNIT.SENSORS_STATUS(1);
|
|
78
78
|
|
|
79
79
|
let tree;
|
|
80
80
|
|
|
@@ -452,7 +452,50 @@ describe('Test UnitDetail', () => {
|
|
|
452
452
|
expect(fullView).toHaveLength(1);
|
|
453
453
|
expect(fullView[0].props.isVisible).toEqual(false);
|
|
454
454
|
});
|
|
455
|
+
test('onPress subunit camera devices', async () => {
|
|
456
|
+
const unitData = {
|
|
457
|
+
stations: [
|
|
458
|
+
{
|
|
459
|
+
camera_devices: [
|
|
460
|
+
{
|
|
461
|
+
configuration: {
|
|
462
|
+
id: 4,
|
|
463
|
+
name: 'Camera cửa nhà xe',
|
|
464
|
+
uri: 'rtsp://admin:hd543211@/ISAPI/Streaming/Channels/101/',
|
|
465
|
+
preview_uri:
|
|
466
|
+
'rtsp://admin:hd543211@/ISAPI/Streaming/Channels/101/',
|
|
467
|
+
playback: 'rtsp://admin:hd543211@/Streaming/tracks/101/',
|
|
468
|
+
},
|
|
469
|
+
id: 41,
|
|
470
|
+
order: 1,
|
|
471
|
+
template: 'camera',
|
|
472
|
+
type: 'camera',
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
},
|
|
476
|
+
],
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
await act(async () => {
|
|
480
|
+
tree = await renderer.create(
|
|
481
|
+
wrapComponent({ params: { ...route.params, unitData } }, account)
|
|
482
|
+
);
|
|
483
|
+
});
|
|
484
|
+
const instance = tree.root;
|
|
485
|
+
const CameraDeviceViews = instance.findAllByType(CameraDevice);
|
|
486
|
+
expect(CameraDeviceViews).toHaveLength(1);
|
|
487
|
+
const goDetailButton = tree.root.findAll(
|
|
488
|
+
(el) =>
|
|
489
|
+
el.props.testID === TESTID.SUB_UNIT_GO_DETAIL &&
|
|
490
|
+
el.type === TouchableOpacity
|
|
491
|
+
);
|
|
455
492
|
|
|
493
|
+
expect(goDetailButton).toHaveLength(2);
|
|
494
|
+
await act(async () => {
|
|
495
|
+
await goDetailButton[0].props.onPress();
|
|
496
|
+
});
|
|
497
|
+
expect(mockedNavigate).toHaveBeenCalled();
|
|
498
|
+
});
|
|
456
499
|
test('render subunit favorites', async () => {
|
|
457
500
|
const unitData = {
|
|
458
501
|
stations: [
|
|
@@ -11,6 +11,17 @@ import { mockSCStore } from '../../../context/mockStore';
|
|
|
11
11
|
|
|
12
12
|
jest.mock('axios');
|
|
13
13
|
|
|
14
|
+
const mockNavigate = jest.fn();
|
|
15
|
+
jest.mock('@react-navigation/native', () => {
|
|
16
|
+
return {
|
|
17
|
+
...jest.requireActual('@react-navigation/native'),
|
|
18
|
+
useRoute: jest.fn(),
|
|
19
|
+
useNavigation: () => ({
|
|
20
|
+
navigate: mockNavigate,
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
14
25
|
const mockedDispatch = jest.fn();
|
|
15
26
|
jest.mock('react-redux', () => ({
|
|
16
27
|
useSelector: jest.fn(),
|
|
@@ -42,6 +53,11 @@ describe('Test Manage Unit', () => {
|
|
|
42
53
|
},
|
|
43
54
|
};
|
|
44
55
|
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
mockNavigate.mockClear();
|
|
58
|
+
axios.get.mockClear();
|
|
59
|
+
});
|
|
60
|
+
|
|
45
61
|
const getElement = (instance) => {
|
|
46
62
|
const changeName = instance.findAll(
|
|
47
63
|
(item) => item.props.testID === TESTID.MANAGE_UNIT_CHANGE_NAME
|
|
@@ -207,4 +223,57 @@ describe('Test Manage Unit', () => {
|
|
|
207
223
|
spyToast.mockReset();
|
|
208
224
|
spyToast.mockRestore();
|
|
209
225
|
});
|
|
226
|
+
test('test onPress onUpdateImage', async () => {
|
|
227
|
+
await act(async () => {
|
|
228
|
+
tree = create(wrapComponent(route));
|
|
229
|
+
});
|
|
230
|
+
const instance = tree.root;
|
|
231
|
+
|
|
232
|
+
const imagePicker = instance.find(
|
|
233
|
+
(item) => item.props.testID === TESTID.MANAGE_UNIT_IMAGE_PICKER
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const handleChoosePhoto = instance.findAll(
|
|
237
|
+
(item) => item.props.testID === TESTID.MANAGE_UNIT_CHANGE_PHOTO
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
act(() => {
|
|
241
|
+
handleChoosePhoto[0].props.onPress();
|
|
242
|
+
handleChoosePhoto[1].props.onPress();
|
|
243
|
+
imagePicker.props.setImageUrl('abc');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
expect(axios.patch).toBeCalled();
|
|
247
|
+
expect(imagePicker.props.showImagePicker).toBeTruthy();
|
|
248
|
+
});
|
|
249
|
+
test('test onPress goSelectLocation', async () => {
|
|
250
|
+
await act(async () => {
|
|
251
|
+
tree = create(wrapComponent(route));
|
|
252
|
+
});
|
|
253
|
+
const instance = tree.root;
|
|
254
|
+
|
|
255
|
+
const buttonWrapper = instance.find(
|
|
256
|
+
(item) => item.props.testID === TESTID.MANAGE_UNIT_CHANGE_LOCATION
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
act(() => {
|
|
260
|
+
buttonWrapper.props.onPress();
|
|
261
|
+
});
|
|
262
|
+
expect(mockNavigate).toBeCalled();
|
|
263
|
+
});
|
|
264
|
+
test('test onPress goToManageSubUnit', async () => {
|
|
265
|
+
await act(async () => {
|
|
266
|
+
tree = create(wrapComponent(route));
|
|
267
|
+
});
|
|
268
|
+
const instance = tree.root;
|
|
269
|
+
|
|
270
|
+
const buttonWrapper = instance.find(
|
|
271
|
+
(item) => item.props.testID === TESTID.MANAGE_UNIT_GO_TO_SUBUNIT
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
act(() => {
|
|
275
|
+
buttonWrapper.props.onPress();
|
|
276
|
+
});
|
|
277
|
+
expect(mockNavigate).toBeCalled();
|
|
278
|
+
});
|
|
210
279
|
});
|
|
@@ -6,21 +6,22 @@ import { SCProvider } from '../../../context';
|
|
|
6
6
|
import { mockSCStore } from '../../../context/mockStore';
|
|
7
7
|
import ListSmartAccount from '../SmartAccount';
|
|
8
8
|
import { SmartAccountItem } from '../SmartAccountItem';
|
|
9
|
-
import { MenuActionMore } from '../../../commons';
|
|
9
|
+
import { AlertAction, MenuActionMore } from '../../../commons';
|
|
10
|
+
import Routes from '../../../utils/Route';
|
|
10
11
|
|
|
11
12
|
const wrapComponent = (route, navigation) => (
|
|
12
13
|
<SCProvider initState={mockSCStore({})}>
|
|
13
|
-
<ListSmartAccount />
|
|
14
|
+
<ListSmartAccount route={route} />
|
|
14
15
|
</SCProvider>
|
|
15
16
|
);
|
|
16
17
|
|
|
17
|
-
const
|
|
18
|
-
|
|
18
|
+
const mockNavigate = jest.fn();
|
|
19
19
|
jest.mock('@react-navigation/native', () => {
|
|
20
20
|
return {
|
|
21
21
|
...jest.requireActual('@react-navigation/native'),
|
|
22
|
+
useRoute: jest.fn(),
|
|
22
23
|
useNavigation: () => ({
|
|
23
|
-
|
|
24
|
+
navigate: mockNavigate,
|
|
24
25
|
}),
|
|
25
26
|
};
|
|
26
27
|
});
|
|
@@ -35,8 +36,13 @@ jest.mock('react', () => {
|
|
|
35
36
|
jest.mock('axios');
|
|
36
37
|
describe('Test SmartAccount', () => {
|
|
37
38
|
let tree;
|
|
39
|
+
let route = {
|
|
40
|
+
params: {
|
|
41
|
+
unitId: 1,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
38
44
|
test('test render SmartAccount', async () => {
|
|
39
|
-
|
|
45
|
+
const reponse = {
|
|
40
46
|
status: 200,
|
|
41
47
|
data: [
|
|
42
48
|
{
|
|
@@ -58,14 +64,25 @@ describe('Test SmartAccount', () => {
|
|
|
58
64
|
username: 'loitest3',
|
|
59
65
|
},
|
|
60
66
|
],
|
|
61
|
-
}
|
|
67
|
+
};
|
|
68
|
+
axios.get.mockImplementationOnce(() => reponse);
|
|
62
69
|
await act(async () => {
|
|
63
|
-
tree = await renderer.create(wrapComponent());
|
|
70
|
+
tree = await renderer.create(wrapComponent(route));
|
|
64
71
|
});
|
|
65
72
|
const instance = tree.root;
|
|
66
73
|
|
|
67
74
|
const smartAccountItem = instance.findAllByType(SmartAccountItem);
|
|
68
75
|
expect(smartAccountItem.length).toEqual(3);
|
|
76
|
+
act(() => {
|
|
77
|
+
smartAccountItem[0].props.gotoSmartAccountDetail(reponse.data[0]);
|
|
78
|
+
smartAccountItem[0].props.onShowMenuMore(reponse.data[0]);
|
|
79
|
+
});
|
|
80
|
+
expect(mockNavigate).toBeCalledWith(Routes.ListDeviceSmartAccount, {
|
|
81
|
+
username: reponse.data[0].username,
|
|
82
|
+
brand: reponse.data[0].brand,
|
|
83
|
+
smart_account_id: reponse.data[0].id,
|
|
84
|
+
unit_id: route.params.unitId,
|
|
85
|
+
});
|
|
69
86
|
});
|
|
70
87
|
|
|
71
88
|
test('test render SmartAccountItem', async () => {
|
|
@@ -79,4 +96,16 @@ describe('Test SmartAccount', () => {
|
|
|
79
96
|
});
|
|
80
97
|
expect(menuActionMore.props.isVisible).toEqual(false);
|
|
81
98
|
});
|
|
99
|
+
|
|
100
|
+
test('test render AlertAction', async () => {
|
|
101
|
+
await act(async () => {
|
|
102
|
+
tree = await renderer.create(wrapComponent());
|
|
103
|
+
});
|
|
104
|
+
const instance = tree.root;
|
|
105
|
+
const alertAction = instance.findByType(AlertAction);
|
|
106
|
+
await act(async () => {
|
|
107
|
+
alertAction.props.leftButtonClick();
|
|
108
|
+
});
|
|
109
|
+
expect(alertAction).toBeDefined();
|
|
110
|
+
});
|
|
82
111
|
});
|
package/src/utils/Apis/axios.js
CHANGED
|
@@ -151,7 +151,10 @@ export async function axiosPatch(...options) {
|
|
|
151
151
|
export async function axiosDelete(...options) {
|
|
152
152
|
return await axiosCall('delete', ...options);
|
|
153
153
|
}
|
|
154
|
-
|
|
154
|
+
const convertFilenameImage = (filename) => {
|
|
155
|
+
const filenameConverted = filename?.replace(/HEIC/g, 'jpg');
|
|
156
|
+
return filename || filenameConverted;
|
|
157
|
+
};
|
|
155
158
|
export function createFormData(data, list_file_field) {
|
|
156
159
|
const formData = new FormData();
|
|
157
160
|
|
|
@@ -165,7 +168,9 @@ export function createFormData(data, list_file_field) {
|
|
|
165
168
|
}
|
|
166
169
|
|
|
167
170
|
formData.append(key, {
|
|
168
|
-
name:
|
|
171
|
+
name: convertFilenameImage(
|
|
172
|
+
item.filename || item.path?.split('/').pop()
|
|
173
|
+
),
|
|
169
174
|
type: item.mime,
|
|
170
175
|
uri: item.path,
|
|
171
176
|
});
|
|
@@ -672,6 +672,7 @@
|
|
|
672
672
|
"text_notification_content_remove_unit_to_member": "Unit **%{unit_name}** has been removed by **%{unit_owner_name}**. You cannot access to this unit anymore.",
|
|
673
673
|
"text_notification_content_remove_member": "You were remove from **%{unit_name}** by **%{unit_owner_name}**. You cannot access to this unit anymore.",
|
|
674
674
|
"text_notification_content_member_leave_unit": "**%{member_name}** has left **%{unit_name}**.",
|
|
675
|
+
"text_notification_content_rename_unit": "Unit **%{old_unit_name}** has been renamed to **%{new_unit_name}** by **%{owner_name}**.",
|
|
675
676
|
"this_spot_does_not_exsit": "This spot does not exist",
|
|
676
677
|
"please_scan_again_or_contact_the_parking_manager": "Please scan again or contact the parking manager",
|
|
677
678
|
"this_spot_does_not_support_to_scan": "This spot does not support to scan",
|
|
@@ -894,8 +895,9 @@
|
|
|
894
895
|
"location_permission_required_wifi_message": "This app needs location permission as this is required to scan for wifi networks.",
|
|
895
896
|
"deny": "DENY",
|
|
896
897
|
"allow": "ALLOW",
|
|
898
|
+
"avatar" : "Avatar",
|
|
897
899
|
"error_share_permission": "{text} {data} does not exist!",
|
|
898
900
|
"text_phone_share_permission": "The phone",
|
|
899
901
|
"text_email_share_permission": "Email",
|
|
900
|
-
"
|
|
902
|
+
"icon_unit": "Icon unit"
|
|
901
903
|
}
|
|
@@ -680,6 +680,7 @@
|
|
|
680
680
|
"text_notification_content_remove_unit_to_member": "Địa điểm **%{unit_name}** vừa được xoá bởi **%{unit_owner_name}**. Bạn không thể truy cập vào địa điểm này được nữa.",
|
|
681
681
|
"text_notification_content_remove_member": "Bạn vừa được xoá khỏi **%{unit_name}** bởi **%{unit_owner_name}**. Bạn không thể truy cập vào địa điểm này được nữa.",
|
|
682
682
|
"text_notification_content_member_leave_unit": "**%{member_name}** vừa rời khỏi **%{unit_name}**.",
|
|
683
|
+
"text_notification_content_rename_unit": "Địa điểm **%{old_unit_name}** vừa được đổi tên thành **%{new_unit_name}** bởi **%{owner_name}**.",
|
|
683
684
|
"this_spot_does_not_exsit": "Vị trí đỗ này không tồn tại",
|
|
684
685
|
"please_scan_again_or_contact_the_parking_manager": "Vui lòng quét lại hoặc liên hệ với người quản lý bãi đậu xe",
|
|
685
686
|
"this_spot_does_not_support_to_scan": "Vị trí đỗ này không hỗ trợ quét",
|
|
@@ -897,8 +898,9 @@
|
|
|
897
898
|
"location_permission_required_wifi_message": "Ứng dụng này cần có quyền định vị vì ứng dụng này được yêu cầu để quét các mạng Wi-Fi.",
|
|
898
899
|
"deny": "Từ chối",
|
|
899
900
|
"allow": "Cho phép",
|
|
901
|
+
"avatar" : "Ảnh đại diện",
|
|
900
902
|
"error_share_permission": "{text} {data} không tồn tại!",
|
|
901
903
|
"text_phone_share_permission": "Số điện thoại",
|
|
902
904
|
"text_email_share_permission": "Email",
|
|
903
|
-
"
|
|
905
|
+
"icon_unit": "Ảnh đại diện địa điểm"
|
|
904
906
|
}
|