@eohjsc/react-native-smart-city 0.2.98 → 0.2.99
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/README.md +35 -14
- package/package.json +1 -1
- package/src/commons/ActionGroup/NumberUpDownActionTemplate.js +2 -2
- package/src/commons/ActionGroup/OnOffSmartLock/OnOffSmartLock.js +3 -3
- package/src/commons/ActionGroup/OnOffTemplate/OnOffSimpleTemplate.js +1 -3
- package/src/commons/ActionGroup/OnOffTemplate/index.js +15 -45
- package/src/commons/ActionGroup/TwoButtonTemplate/index.js +6 -6
- package/src/commons/FieldTemplate/ChooseUserField/ChooseFieldStyles.js +25 -0
- package/src/commons/FieldTemplate/ChooseUserField/ChoosePopup.js +96 -0
- package/src/commons/FieldTemplate/ChooseUserField/ChoosePopupStyles.js +39 -0
- package/src/commons/FieldTemplate/ChooseUserField/__test__/index.test.js +113 -0
- package/src/commons/FieldTemplate/ChooseUserField/index.js +62 -0
- package/src/commons/FieldTemplate/PasscodeField/PasscodeFieldStyles.js +30 -0
- package/src/commons/FieldTemplate/PasscodeField/__test__/index.test.js +93 -0
- package/src/commons/FieldTemplate/PasscodeField/index.js +43 -0
- package/src/commons/FieldTemplate/ScheduleField/ScheduleFieldStyles.js +13 -0
- package/src/commons/FieldTemplate/ScheduleField/__test__/index.test.js +182 -0
- package/src/commons/FieldTemplate/ScheduleField/index.js +176 -0
- package/src/commons/WheelDateTimePicker/index.js +2 -1
- package/src/configs/API.js +3 -0
- package/src/configs/Constants.js +12 -1
- package/src/context/actionType.ts +0 -1
- package/src/context/reducer.ts +0 -3
- package/src/navigations/UnitStack.js +8 -0
- package/src/screens/AddNewAction/SelectSensorDevices.js +2 -1
- package/src/screens/AddNewAutoSmart/__test__/AddNewAutoSmart.test.js +3 -1
- package/src/screens/AddNewAutoSmart/index.js +5 -2
- package/src/screens/AddNewOneTap/__test__/AddNewOneTap.test.js +1 -1
- package/src/screens/AddNewOneTap/index.js +2 -1
- package/src/screens/Device/__test__/detail.test.js +4 -4
- package/src/screens/Device/detail.js +29 -12
- package/src/screens/EmergencyContacts/EmergencyContactsSelectContacts.js +5 -2
- package/src/screens/GuestInfo/__test__/index.test.js +1 -1
- package/src/screens/GuestInfo/components/RecurringDetail.js +1 -0
- package/src/screens/GuestInfo/components/TemporaryDetail.js +2 -2
- package/src/screens/ScanChipQR/components/QRScan/index.js +1 -0
- package/src/screens/ScriptDetail/index.js +1 -1
- package/src/screens/SelectUnit/__test__/index.test.js +1 -1
- package/src/screens/SelectUnit/index.js +4 -2
- package/src/screens/SetSchedule/index.js +2 -1
- package/src/screens/Sharing/SelectPermission.js +0 -1
- package/src/screens/SideMenuDetail/SideMenuDetailStyles.js +28 -0
- package/src/screens/SideMenuDetail/__test__/index.test.js +165 -0
- package/src/screens/SideMenuDetail/index.js +149 -0
- package/src/utils/I18n/translations/en.json +1 -0
- package/src/utils/I18n/translations/vi.json +1 -0
- package/src/utils/Route/index.js +1 -0
package/README.md
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
## react-native-smart-city
|
|
2
2
|
|
|
3
3
|
## Getting started
|
|
4
|
+
|
|
4
5
|
1. Install package dependencies:
|
|
5
|
-
|
|
6
|
+
|
|
7
|
+
- Using [Yarn](https://yarnpkg.com/): `yarn add react-native-reanimated@^1.10.1`
|
|
6
8
|
|
|
7
9
|
2. Install:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
|
|
11
|
+
- Using [npm](https://www.npmjs.com/#getting-started): `npm install @eohjsc/react-native-smart-city --save`
|
|
12
|
+
- Using [Yarn](https://yarnpkg.com/): `yarn add @eohjsc/react-native-smart-city`
|
|
10
13
|
|
|
11
14
|
3. Compile application using react-native run-android
|
|
15
|
+
|
|
12
16
|
### Mostly automatic installation
|
|
17
|
+
|
|
13
18
|
`$ react-native link @eohjsc/react-native-smart-city`
|
|
14
19
|
|
|
15
20
|
## Usage
|
|
21
|
+
|
|
16
22
|
1. StackNavigator
|
|
23
|
+
|
|
17
24
|
```javascript
|
|
18
25
|
import {
|
|
19
26
|
UnitStack,
|
|
@@ -30,7 +37,7 @@ import {
|
|
|
30
37
|
} from '@eohjsc/react-native-smart-city';
|
|
31
38
|
import { createStackNavigator } from '@react-navigation/stack';
|
|
32
39
|
|
|
33
|
-
// TODO: What to do with the module?
|
|
40
|
+
// TODO: What to do with the module?
|
|
34
41
|
const Stack = createStackNavigator();
|
|
35
42
|
|
|
36
43
|
const YourStack = () => {
|
|
@@ -84,7 +91,9 @@ const MainTab = () => {
|
|
|
84
91
|
);
|
|
85
92
|
};
|
|
86
93
|
```
|
|
94
|
+
|
|
87
95
|
2. Use components
|
|
96
|
+
|
|
88
97
|
```javascript
|
|
89
98
|
import React from 'react';
|
|
90
99
|
import { View } from 'react-native';
|
|
@@ -93,13 +102,15 @@ import { MyPinnedSharedUnit, MyUnit } from '@eohjsc/react-native-smart-city';
|
|
|
93
102
|
const MyScreen = () => {
|
|
94
103
|
return (
|
|
95
104
|
<View>
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
<MyUnit />
|
|
106
|
+
<MyPinnedSharedUnit />
|
|
98
107
|
</View>
|
|
99
108
|
);
|
|
100
109
|
};
|
|
101
110
|
```
|
|
111
|
+
|
|
102
112
|
3. Trigger quick action
|
|
113
|
+
|
|
103
114
|
```javascript
|
|
104
115
|
import React from 'react';
|
|
105
116
|
import { View, Button } from 'react-native';
|
|
@@ -126,23 +137,33 @@ export const MyFunctionalComponent = () => {
|
|
|
126
137
|
lg_actions: [],
|
|
127
138
|
name: 'Garage Up',
|
|
128
139
|
};
|
|
129
|
-
const handleQuickAction = async() => {
|
|
140
|
+
const handleQuickAction = async () => {
|
|
130
141
|
const result = await sendRemoteCommand(sensor, action); // Action Garage Up
|
|
131
|
-
if (result){
|
|
132
|
-
console.log('Successful control')
|
|
133
|
-
}else {
|
|
134
|
-
console.log('Control failed')
|
|
142
|
+
if (result) {
|
|
143
|
+
console.log('Successful control');
|
|
144
|
+
} else {
|
|
145
|
+
console.log('Control failed');
|
|
135
146
|
}
|
|
136
147
|
};
|
|
137
148
|
return (
|
|
138
149
|
<View>
|
|
139
|
-
<Button
|
|
150
|
+
<Button
|
|
151
|
+
onPress={handleQuickAction}
|
|
152
|
+
title="Example trigger quick action control Garage Up"
|
|
153
|
+
/>
|
|
140
154
|
</View>
|
|
141
155
|
);
|
|
142
156
|
};
|
|
143
157
|
```
|
|
144
|
-
|
|
145
|
-
- `
|
|
158
|
+
|
|
159
|
+
- sendRemoteCommand user needs to `have permission to control the device`, you pass in the `sensor and action` to it and it will run after calling render.
|
|
160
|
+
- `sensor` is a device in your Unit and action is the `action` key of the device..
|
|
146
161
|
- You can take out the sensor and action from your Unit when calling the `API.UNIT.UNIT_DETAIL(unitId)` it will return all devices in 1 unit.
|
|
147
162
|
- Or get sensor from `API.SENSOR.DISPLAY(sensor.id)` returns all actions of 1 device.
|
|
148
163
|
- Refer YourProjectPath /node_modules/@eohjsc/react-native-smart-city/src/screens/Unit/Detail.js
|
|
164
|
+
|
|
165
|
+
## Sync file to EoH source
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
yarn install && yarn watch
|
|
169
|
+
```
|
package/package.json
CHANGED
|
@@ -38,8 +38,8 @@ const NumberUpDownActionTemplate = ({ actionGroup, doAction, sensor }) => {
|
|
|
38
38
|
}, [configValues, config, keep_track_config]);
|
|
39
39
|
|
|
40
40
|
const doActionAndWatchConfig = useCallback(
|
|
41
|
-
(actionData, actionValue, actionName) => {
|
|
42
|
-
doAction(
|
|
41
|
+
async (actionData, actionValue, actionName) => {
|
|
42
|
+
await doAction(
|
|
43
43
|
actionData,
|
|
44
44
|
JSON.stringify({ temperature: actionValue }),
|
|
45
45
|
actionName
|
|
@@ -17,7 +17,7 @@ const OnOffSmartLock = memo(({ actionGroup, doAction, sensor }) => {
|
|
|
17
17
|
const [isUnlock, setisUnlock] = useState(true);
|
|
18
18
|
const [configValues] = useConfigGlobalState('configValues');
|
|
19
19
|
|
|
20
|
-
const handleActionSmartLock = useCallback(() => {
|
|
20
|
+
const handleActionSmartLock = useCallback(async () => {
|
|
21
21
|
if (action_on_data && action_off_data) {
|
|
22
22
|
if (isUnlock) {
|
|
23
23
|
let actionName = `${
|
|
@@ -25,14 +25,14 @@ const OnOffSmartLock = memo(({ actionGroup, doAction, sensor }) => {
|
|
|
25
25
|
} ${actionGroup?.title?.toLowerCase()} lock`;
|
|
26
26
|
actionName = actionName.replace(/\s+/g, ' ').trim();
|
|
27
27
|
const dataLock = { door_lock: 0 };
|
|
28
|
-
doAction(action_on_data, JSON.stringify(dataLock), actionName);
|
|
28
|
+
await doAction(action_on_data, JSON.stringify(dataLock), actionName);
|
|
29
29
|
} else {
|
|
30
30
|
let actionName = `${
|
|
31
31
|
sensor?.name
|
|
32
32
|
} ${actionGroup?.title?.toLowerCase()} unlock`;
|
|
33
33
|
actionName = actionName.replace(/\s+/g, ' ').trim();
|
|
34
34
|
const dataUnlock = { door_lock: 1 };
|
|
35
|
-
doAction(action_off_data, JSON.stringify(dataUnlock), actionName);
|
|
35
|
+
await doAction(action_off_data, JSON.stringify(dataUnlock), actionName);
|
|
36
36
|
}
|
|
37
37
|
if (sensor?.is_managed_by_backend) {
|
|
38
38
|
configuration.config &&
|
|
@@ -11,8 +11,6 @@ const OnOffSimpleTemplate = ({
|
|
|
11
11
|
triggerAction,
|
|
12
12
|
actionGroup,
|
|
13
13
|
disabled,
|
|
14
|
-
valueOfSwitch,
|
|
15
|
-
checkValueOfSwitch,
|
|
16
14
|
}) => {
|
|
17
15
|
return (
|
|
18
16
|
<View style={styles.wrap}>
|
|
@@ -26,7 +24,7 @@ const OnOffSimpleTemplate = ({
|
|
|
26
24
|
thumbColor={isOn ? Colors.White : Colors.Gray6}
|
|
27
25
|
ios_backgroundColor={Colors.Gray4}
|
|
28
26
|
onValueChange={triggerAction}
|
|
29
|
-
value={
|
|
27
|
+
value={!!isOn}
|
|
30
28
|
disabled={disabled}
|
|
31
29
|
/>
|
|
32
30
|
)}
|
|
@@ -1,19 +1,10 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
memo,
|
|
3
|
-
useCallback,
|
|
4
|
-
useContext,
|
|
5
|
-
useEffect,
|
|
6
|
-
useMemo,
|
|
7
|
-
useState,
|
|
8
|
-
} from 'react';
|
|
1
|
+
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
|
9
2
|
import { DEVICE_TYPE } from '../../../configs/Constants';
|
|
10
3
|
|
|
11
4
|
import { watchMultiConfigs } from '../../../iot/Monitor';
|
|
12
5
|
import { useConfigGlobalState } from '../../../iot/states';
|
|
13
6
|
import OnOffButtonTemplate from './OnOffButtonTemplate';
|
|
14
7
|
import OnOffSimpleTemplate from './OnOffSimpleTemplate';
|
|
15
|
-
import { SCContext } from '../../../context';
|
|
16
|
-
import { Action } from '../../../context/actionType';
|
|
17
8
|
|
|
18
9
|
const getComponent = (template) => {
|
|
19
10
|
switch (template) {
|
|
@@ -30,13 +21,9 @@ const OnOffTemplate = memo(({ actionGroup, doAction, sensor }) => {
|
|
|
30
21
|
const { configuration } = actionGroup;
|
|
31
22
|
const { action_data, action_on_data, action_off_data } = configuration;
|
|
32
23
|
const [isOn, setIsOn] = useState(null);
|
|
33
|
-
const [valueOfSwitch, setValueOfSwitch] = useState(false);
|
|
34
|
-
const [checkValueOfSwitch, setCheckValueOfSwitch] = useState(true);
|
|
35
|
-
//checkValueOfSwitch to check the value real-time of Switch when re-entering the screen
|
|
36
24
|
|
|
37
25
|
// eslint-disable-next-line no-unused-vars
|
|
38
26
|
const [configValues, _] = useConfigGlobalState('configValues');
|
|
39
|
-
const { setAction } = useContext(SCContext);
|
|
40
27
|
|
|
41
28
|
const actionName = useCallback(
|
|
42
29
|
(text) => {
|
|
@@ -47,62 +34,48 @@ const OnOffTemplate = memo(({ actionGroup, doAction, sensor }) => {
|
|
|
47
34
|
},
|
|
48
35
|
[actionGroup?.title, sensor?.name]
|
|
49
36
|
);
|
|
50
|
-
const triggerAction = useCallback(() => {
|
|
51
|
-
if (actionGroup?.template === 'OnOffSimpleActionTemplate') {
|
|
52
|
-
setAction(Action.IS_FULL_LOADING, true);
|
|
53
|
-
}
|
|
54
|
-
setCheckValueOfSwitch(false);
|
|
37
|
+
const triggerAction = useCallback(async () => {
|
|
55
38
|
switch (sensor?.device_type) {
|
|
56
39
|
case DEVICE_TYPE.ZIGBEE:
|
|
57
40
|
if (action_on_data && action_off_data) {
|
|
58
41
|
if (isOn) {
|
|
59
|
-
doAction(
|
|
42
|
+
await doAction(
|
|
60
43
|
action_off_data,
|
|
61
44
|
JSON.stringify({ state: 0 }),
|
|
62
45
|
actionName('off')
|
|
63
46
|
);
|
|
64
47
|
} else {
|
|
65
|
-
doAction(
|
|
48
|
+
await doAction(
|
|
66
49
|
action_on_data,
|
|
67
50
|
JSON.stringify({ state: 1 }),
|
|
68
51
|
actionName('on')
|
|
69
52
|
);
|
|
70
53
|
}
|
|
71
54
|
}
|
|
72
|
-
|
|
55
|
+
break;
|
|
73
56
|
default:
|
|
74
57
|
if (action_data) {
|
|
75
58
|
if (isOn) {
|
|
76
|
-
doAction(action_data, false);
|
|
59
|
+
await doAction(action_data, false);
|
|
77
60
|
} else {
|
|
78
|
-
doAction(action_data, true);
|
|
61
|
+
await doAction(action_data, true);
|
|
79
62
|
}
|
|
80
63
|
}
|
|
81
64
|
if (action_on_data && action_off_data) {
|
|
82
|
-
setValueOfSwitch(!isOn);
|
|
83
65
|
if (isOn) {
|
|
84
|
-
doAction(action_off_data, null, actionName('off'));
|
|
66
|
+
await doAction(action_off_data, null, actionName('off'));
|
|
85
67
|
} else {
|
|
86
|
-
doAction(action_on_data, null, actionName('on'));
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if (sensor?.is_managed_by_backend) {
|
|
90
|
-
configuration?.config &&
|
|
91
|
-
sensor?.device_type !== 'GOOGLE_HOME' &&
|
|
92
|
-
watchMultiConfigs([configuration?.config]);
|
|
93
|
-
if (actionGroup?.template === 'OnOffSimpleActionTemplate') {
|
|
94
|
-
const cleanTimeout = setTimeout(() => {
|
|
95
|
-
setAction(Action.IS_FULL_LOADING, false);
|
|
96
|
-
clearTimeout(cleanTimeout);
|
|
97
|
-
}, 2000);
|
|
68
|
+
await doAction(action_on_data, null, actionName('on'));
|
|
98
69
|
}
|
|
99
|
-
} else {
|
|
100
|
-
setValueOfSwitch(isOn);
|
|
101
70
|
}
|
|
102
|
-
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
if (sensor?.is_managed_by_backend) {
|
|
74
|
+
configuration?.config &&
|
|
75
|
+
sensor?.device_type !== 'GOOGLE_HOME' &&
|
|
76
|
+
watchMultiConfigs([configuration?.config]);
|
|
103
77
|
}
|
|
104
78
|
}, [
|
|
105
|
-
actionGroup.template,
|
|
106
79
|
actionName,
|
|
107
80
|
action_data,
|
|
108
81
|
action_off_data,
|
|
@@ -112,7 +85,6 @@ const OnOffTemplate = memo(({ actionGroup, doAction, sensor }) => {
|
|
|
112
85
|
isOn,
|
|
113
86
|
sensor?.device_type,
|
|
114
87
|
sensor?.is_managed_by_backend,
|
|
115
|
-
setAction,
|
|
116
88
|
]);
|
|
117
89
|
|
|
118
90
|
useEffect(() => {
|
|
@@ -150,8 +122,6 @@ const OnOffTemplate = memo(({ actionGroup, doAction, sensor }) => {
|
|
|
150
122
|
triggerAction={triggerAction}
|
|
151
123
|
actionGroup={actionGroup}
|
|
152
124
|
disabled={!action_data && !action_on_data && !action_off_data}
|
|
153
|
-
valueOfSwitch={valueOfSwitch}
|
|
154
|
-
checkValueOfSwitch={checkValueOfSwitch}
|
|
155
125
|
/>
|
|
156
126
|
</>
|
|
157
127
|
);
|
|
@@ -28,12 +28,12 @@ const TwoButtonTemplate = memo(({ actionGroup, doAction, sensor }) => {
|
|
|
28
28
|
[actionGroup?.title, sensor?.name]
|
|
29
29
|
);
|
|
30
30
|
|
|
31
|
-
const triggerAction1 = useCallback(() => {
|
|
31
|
+
const triggerAction1 = useCallback(async () => {
|
|
32
32
|
if (button1?.action_on_data && button1?.action_off_data) {
|
|
33
33
|
if (isOn) {
|
|
34
|
-
doAction(button1?.action_off_data, null, actionName('off'));
|
|
34
|
+
await doAction(button1?.action_off_data, null, actionName('off'));
|
|
35
35
|
} else {
|
|
36
|
-
doAction(button1?.action_on_data, null, actionName('on'));
|
|
36
|
+
await doAction(button1?.action_on_data, null, actionName('on'));
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
if (sensor?.is_managed_by_backend) {
|
|
@@ -51,12 +51,12 @@ const TwoButtonTemplate = memo(({ actionGroup, doAction, sensor }) => {
|
|
|
51
51
|
sensor?.device_type,
|
|
52
52
|
]);
|
|
53
53
|
|
|
54
|
-
const triggerAction2 = useCallback(() => {
|
|
54
|
+
const triggerAction2 = useCallback(async () => {
|
|
55
55
|
if (button2?.action_on_data && button2?.action_off_data) {
|
|
56
56
|
if (isStart) {
|
|
57
|
-
doAction(button2?.action_off_data, null, actionName('stop'));
|
|
57
|
+
await doAction(button2?.action_off_data, null, actionName('stop'));
|
|
58
58
|
} else {
|
|
59
|
-
doAction(button2?.action_on_data, null, actionName('start'));
|
|
59
|
+
await doAction(button2?.action_on_data, null, actionName('start'));
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
if (sensor?.is_managed_by_backend && sensor.device_type !== 'GOOGLE_HOME') {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import { Colors } from '../../../configs';
|
|
3
|
+
|
|
4
|
+
export default StyleSheet.create({
|
|
5
|
+
wrapper: {
|
|
6
|
+
flexDirection: 'row',
|
|
7
|
+
paddingTop: 30,
|
|
8
|
+
},
|
|
9
|
+
textHeadLine: {
|
|
10
|
+
fontSize: 16,
|
|
11
|
+
color: Colors.Gray9,
|
|
12
|
+
fontWeight: 'bold',
|
|
13
|
+
},
|
|
14
|
+
buttonValue: {
|
|
15
|
+
flex: 1,
|
|
16
|
+
flexDirection: 'row',
|
|
17
|
+
justifyContent: 'flex-end',
|
|
18
|
+
alignItems: 'center',
|
|
19
|
+
},
|
|
20
|
+
value: {
|
|
21
|
+
marginRight: 20,
|
|
22
|
+
fontSize: 14,
|
|
23
|
+
color: Colors.Gray8,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React, { useState, useCallback } from 'react';
|
|
2
|
+
import { View, ScrollView, TouchableOpacity } from 'react-native';
|
|
3
|
+
import { Colors } from '../../../configs';
|
|
4
|
+
import { TESTID } from '../../../configs/Constants';
|
|
5
|
+
import { useTranslations } from '../../../hooks/Common/useTranslations';
|
|
6
|
+
import BottomButtonView from '../../BottomButtonView';
|
|
7
|
+
import { ModalCustom } from '../../Modal';
|
|
8
|
+
import RadioCircle from '../../RadioCircle';
|
|
9
|
+
import Text from '../../Text';
|
|
10
|
+
|
|
11
|
+
import styles from './ChoosePopupStyles';
|
|
12
|
+
|
|
13
|
+
const RowMember = ({ member, isSelected, onSelect }) => {
|
|
14
|
+
const handleOnPress = useCallback(() => {
|
|
15
|
+
onSelect(member);
|
|
16
|
+
}, [onSelect, member]);
|
|
17
|
+
return (
|
|
18
|
+
<TouchableOpacity
|
|
19
|
+
style={styles.row}
|
|
20
|
+
onPress={handleOnPress}
|
|
21
|
+
testID={TESTID.CHOOSE_ITEM}
|
|
22
|
+
>
|
|
23
|
+
<RadioCircle active={isSelected} />
|
|
24
|
+
<View style={styles.wrapText}>
|
|
25
|
+
<Text type="H4" color={Colors.Gray9}>
|
|
26
|
+
{member.name
|
|
27
|
+
? member.name
|
|
28
|
+
: member.phone_number
|
|
29
|
+
? member.phone_number
|
|
30
|
+
: member.email}
|
|
31
|
+
</Text>
|
|
32
|
+
</View>
|
|
33
|
+
</TouchableOpacity>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const ChoosePopup = ({ isVisible, title, onHide, members, onApply }) => {
|
|
38
|
+
const t = useTranslations();
|
|
39
|
+
const [selectedUsers, setSelectedUsers] = useState({});
|
|
40
|
+
|
|
41
|
+
const handleOnSelectUser = useCallback((user) => {
|
|
42
|
+
setSelectedUsers(user);
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
const handleOnApply = useCallback(() => {
|
|
46
|
+
onApply(selectedUsers);
|
|
47
|
+
onHide();
|
|
48
|
+
}, [selectedUsers, onApply, onHide]);
|
|
49
|
+
|
|
50
|
+
const handleOnCancel = useCallback(() => {
|
|
51
|
+
onHide();
|
|
52
|
+
}, [onHide]);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<ModalCustom
|
|
56
|
+
testID={TESTID.CHOOSE_POPUP}
|
|
57
|
+
isVisible={isVisible}
|
|
58
|
+
onBackButtonPress={handleOnCancel}
|
|
59
|
+
onBackdropPress={handleOnCancel}
|
|
60
|
+
useNativeDriver={true}
|
|
61
|
+
useNativeDriverForBackdrop={true}
|
|
62
|
+
animationIn={'zoomIn'}
|
|
63
|
+
animationOut={'zoomOut'}
|
|
64
|
+
style={styles.wrapPopup}
|
|
65
|
+
>
|
|
66
|
+
<View style={[styles.popup, styles.heigh50percent]}>
|
|
67
|
+
<Text type="H4" style={styles.title} bold>
|
|
68
|
+
{title}
|
|
69
|
+
</Text>
|
|
70
|
+
<View style={styles.separator} />
|
|
71
|
+
<ScrollView>
|
|
72
|
+
{members.map((member, index) => (
|
|
73
|
+
<RowMember
|
|
74
|
+
key={index}
|
|
75
|
+
member={member}
|
|
76
|
+
isSelected={selectedUsers.id === member.id}
|
|
77
|
+
onSelect={handleOnSelectUser}
|
|
78
|
+
/>
|
|
79
|
+
))}
|
|
80
|
+
</ScrollView>
|
|
81
|
+
<View style={styles.separator} />
|
|
82
|
+
<BottomButtonView
|
|
83
|
+
rowButton={true}
|
|
84
|
+
testIDPrefix={TESTID.CHOOSE_POPUP}
|
|
85
|
+
style={styles.bottomButtonView}
|
|
86
|
+
secondaryTitle={t('cancel')}
|
|
87
|
+
mainTitle={t('apply')}
|
|
88
|
+
onPressSecondary={handleOnCancel}
|
|
89
|
+
onPressMain={handleOnApply}
|
|
90
|
+
/>
|
|
91
|
+
</View>
|
|
92
|
+
</ModalCustom>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export default ChoosePopup;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import { Colors, Constants } from '../../../configs';
|
|
3
|
+
|
|
4
|
+
export default StyleSheet.create({
|
|
5
|
+
wrapPopup: {
|
|
6
|
+
marginHorizontal: 16,
|
|
7
|
+
},
|
|
8
|
+
popup: {
|
|
9
|
+
backgroundColor: Colors.White,
|
|
10
|
+
width: '100%',
|
|
11
|
+
height: (Constants.height * 80) / 100,
|
|
12
|
+
padding: 24,
|
|
13
|
+
paddingBottom: 100,
|
|
14
|
+
borderRadius: 10,
|
|
15
|
+
},
|
|
16
|
+
heigh50percent: {
|
|
17
|
+
height: (Constants.height * 50) / 100,
|
|
18
|
+
},
|
|
19
|
+
title: {
|
|
20
|
+
marginBottom: 16,
|
|
21
|
+
},
|
|
22
|
+
bottomButtonView: {
|
|
23
|
+
marginBottom: 24,
|
|
24
|
+
},
|
|
25
|
+
separator: {
|
|
26
|
+
height: 0.5,
|
|
27
|
+
backgroundColor: Colors.Gray4,
|
|
28
|
+
},
|
|
29
|
+
row: {
|
|
30
|
+
flexDirection: 'row',
|
|
31
|
+
alignItems: 'center',
|
|
32
|
+
},
|
|
33
|
+
wrapText: {
|
|
34
|
+
flex: 1,
|
|
35
|
+
paddingVertical: 8,
|
|
36
|
+
marginRight: 24,
|
|
37
|
+
marginLeft: 16,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { act, create } from 'react-test-renderer';
|
|
4
|
+
import { TESTID } from '../../../../configs/Constants';
|
|
5
|
+
import { mockSCStore } from '../../../../context/mockStore';
|
|
6
|
+
import { SCProvider } from '../../../../context';
|
|
7
|
+
import ChooseUserField from '../index';
|
|
8
|
+
import { TouchableOpacity } from 'react-native';
|
|
9
|
+
import { ModalCustom } from '../../../Modal';
|
|
10
|
+
|
|
11
|
+
jest.mock('axios');
|
|
12
|
+
|
|
13
|
+
const wrapComponent = (
|
|
14
|
+
unit = { id: 1 },
|
|
15
|
+
dataItem = {
|
|
16
|
+
type: 'choose_user',
|
|
17
|
+
key: 'user_id',
|
|
18
|
+
action: {
|
|
19
|
+
type: 'action_zigbee',
|
|
20
|
+
id: 1,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
index = 0,
|
|
24
|
+
setDataForm,
|
|
25
|
+
dataForm = [
|
|
26
|
+
{
|
|
27
|
+
type: 'choose_user',
|
|
28
|
+
key: 'user_id',
|
|
29
|
+
action: {
|
|
30
|
+
type: 'action_zigbee',
|
|
31
|
+
id: 1,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
) => (
|
|
36
|
+
<SCProvider initState={mockSCStore({})}>
|
|
37
|
+
<ChooseUserField
|
|
38
|
+
unit={unit}
|
|
39
|
+
dataItem={dataItem}
|
|
40
|
+
index={index}
|
|
41
|
+
setDataForm={jest.fn()}
|
|
42
|
+
dataForm={dataForm}
|
|
43
|
+
/>
|
|
44
|
+
</SCProvider>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
describe('Test ChooseUserField', () => {
|
|
48
|
+
let tree;
|
|
49
|
+
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
axios.get.mockClear();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const onPressCancel = async (instance, popup) => {
|
|
55
|
+
const buttonCancel = instance.find(
|
|
56
|
+
(el) =>
|
|
57
|
+
el.props.testID ===
|
|
58
|
+
TESTID.CHOOSE_POPUP + TESTID.BOTTOM_VIEW_SECONDARY &&
|
|
59
|
+
el.type === TouchableOpacity
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
await act(async () => {
|
|
63
|
+
await buttonCancel.props.onPress();
|
|
64
|
+
});
|
|
65
|
+
expect(popup.props.isVisible).toBeFalsy();
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const onPressApply = async (instance, popup) => {
|
|
69
|
+
const buttonApply = instance.find(
|
|
70
|
+
(el) =>
|
|
71
|
+
el.props.testID === TESTID.CHOOSE_POPUP + TESTID.BOTTOM_VIEW_MAIN &&
|
|
72
|
+
el.type === TouchableOpacity
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
await act(async () => {
|
|
76
|
+
await buttonApply.props.onPress();
|
|
77
|
+
});
|
|
78
|
+
expect(popup.props.isVisible).toBeFalsy();
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
test('render ChooseUserField handleOnApply', async () => {
|
|
82
|
+
const response_unit_members = {
|
|
83
|
+
status: 200,
|
|
84
|
+
data: [{ id: 1, name: 'Ken' }],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
axios.get.mockImplementation(() => {
|
|
88
|
+
return response_unit_members;
|
|
89
|
+
});
|
|
90
|
+
await act(async () => {
|
|
91
|
+
tree = create(wrapComponent());
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const instance = tree.root;
|
|
95
|
+
|
|
96
|
+
const ChooseField = instance.find(
|
|
97
|
+
(item) =>
|
|
98
|
+
item.props.testID === TESTID.CHOOSE_FIELD &&
|
|
99
|
+
item.type === TouchableOpacity
|
|
100
|
+
);
|
|
101
|
+
await act(async () => {
|
|
102
|
+
await ChooseField.props.onPress();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const popup = instance.find(
|
|
106
|
+
(item) =>
|
|
107
|
+
item.props.testID === TESTID.CHOOSE_POPUP && item.type === ModalCustom
|
|
108
|
+
);
|
|
109
|
+
expect(popup.props.isVisible).toBeTruthy();
|
|
110
|
+
await onPressCancel(instance, popup);
|
|
111
|
+
await onPressApply(instance, popup);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React, { memo, useEffect, useState } from 'react';
|
|
2
|
+
import { IconOutline } from '@ant-design/icons-react-native';
|
|
3
|
+
import { TouchableOpacity, View } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import styles from './ChooseFieldStyles';
|
|
6
|
+
import ChoosePopup from './ChoosePopup';
|
|
7
|
+
import { useBoolean } from '../../../hooks/Common';
|
|
8
|
+
import { axiosGet } from '../../../utils/Apis/axios';
|
|
9
|
+
import { API, Colors } from '../../../configs';
|
|
10
|
+
import { TESTID } from '../../../configs/Constants';
|
|
11
|
+
import Text from '../../Text';
|
|
12
|
+
import { useTranslations } from '../../../hooks/Common/useTranslations';
|
|
13
|
+
|
|
14
|
+
const ChooseUserField = ({ unit, dataItem, index, setDataForm, dataForm }) => {
|
|
15
|
+
const [showFilterPopup, setShowFilterPopup, setHideFilterPopup] =
|
|
16
|
+
useBoolean();
|
|
17
|
+
const [choose, setChoose] = useState({});
|
|
18
|
+
const [members, setMembers] = useState([]);
|
|
19
|
+
const t = useTranslations();
|
|
20
|
+
const fetchMembers = async () => {
|
|
21
|
+
const { success, data } = await axiosGet(API.SHARE.UNITS_MEMBERS(unit.id));
|
|
22
|
+
if (success) {
|
|
23
|
+
setMembers(data);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!members.length) {
|
|
29
|
+
fetchMembers();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
dataItem.data = choose.id;
|
|
34
|
+
dataItem.valid = true;
|
|
35
|
+
dataItem[dataItem.action.type] = dataItem.action.id;
|
|
36
|
+
dataForm[index] = dataItem;
|
|
37
|
+
setDataForm([...dataForm]);
|
|
38
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
39
|
+
}, [choose]);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<View style={styles.wrapper}>
|
|
43
|
+
<Text style={styles.textHeadLine}>{t('choose_user')}</Text>
|
|
44
|
+
<TouchableOpacity
|
|
45
|
+
testID={TESTID.CHOOSE_FIELD}
|
|
46
|
+
style={styles.buttonValue}
|
|
47
|
+
onPress={setShowFilterPopup}
|
|
48
|
+
>
|
|
49
|
+
<Text style={styles.value}>{choose.name}</Text>
|
|
50
|
+
<IconOutline name="right" size={20} color={Colors.Gray8} />
|
|
51
|
+
</TouchableOpacity>
|
|
52
|
+
<ChoosePopup
|
|
53
|
+
isVisible={showFilterPopup}
|
|
54
|
+
onHide={setHideFilterPopup}
|
|
55
|
+
members={members}
|
|
56
|
+
title={t('choose_user')}
|
|
57
|
+
onApply={setChoose}
|
|
58
|
+
/>
|
|
59
|
+
</View>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
export default memo(ChooseUserField);
|