@eohjsc/react-native-smart-city 0.7.14 → 0.7.16

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 (33) hide show
  1. package/assets/images/Sms.svg +9 -0
  2. package/package.json +1 -1
  3. package/src/commons/Automate/ItemConditionScriptDetailStyles.js +1 -0
  4. package/src/commons/Widgets/IFrame/IFrame.js +2 -2
  5. package/src/commons/Widgets/IFrame/IFrameStyles.js +5 -0
  6. package/src/configs/API.js +8 -2
  7. package/src/hooks/Common/useBlockBack.js +36 -21
  8. package/src/hooks/Common/useDevicesStatus.js +16 -13
  9. package/src/navigations/UnitStack.js +24 -0
  10. package/src/screens/AddNewGateway/ScanDeviceLocal.js +14 -16
  11. package/src/screens/AddNewGateway/ScanDeviceLocalStyles.js +6 -1
  12. package/src/screens/AddNewGateway/__test__/ScanDeviceLocal.test.js +4 -13
  13. package/src/screens/Automate/AddNewAction/SetupScriptReceiverSms.js +167 -0
  14. package/src/screens/Automate/AddNewAction/SetupScriptSms.js +73 -0
  15. package/src/screens/Automate/AddNewAction/Styles/SetupScriptEmailStyles.js +5 -0
  16. package/src/screens/Automate/AddNewAction/__test__/SetupScriptReceiverSms.test.js +105 -0
  17. package/src/screens/Automate/AddNewAction/__test__/SetupScriptSms.test.js +70 -0
  18. package/src/screens/Automate/EditActionsList/UpdateReceiverSmsScript.js +178 -0
  19. package/src/screens/Automate/EditActionsList/UpdateSmsScript.js +66 -0
  20. package/src/screens/Automate/EditActionsList/__tests__/UpdateReceiverSmsScript.test.js +82 -0
  21. package/src/screens/Automate/EditActionsList/__tests__/UpdateSmsScript.test.js +71 -0
  22. package/src/screens/Automate/EditActionsList/index.js +50 -1
  23. package/src/screens/Automate/ScriptDetail/Components/AddActionScript.js +52 -19
  24. package/src/screens/Automate/ScriptDetail/Styles/indexStyles.js +39 -2
  25. package/src/screens/Automate/ScriptDetail/__test__/index.test.js +106 -38
  26. package/src/screens/Automate/ScriptDetail/index.js +227 -10
  27. package/src/screens/SharedUnit/__test__/ShareUnit.test.js +22 -1
  28. package/src/screens/Sharing/UnitMemberList.js +1 -1
  29. package/src/screens/Sharing/__test__/UnitMemberList.test.js +10 -0
  30. package/src/screens/Unit/__test__/Detail.test.js +1 -1
  31. package/src/utils/I18n/translations/en.js +10 -0
  32. package/src/utils/I18n/translations/vi.js +10 -0
  33. package/src/utils/Route/index.js +3 -0
@@ -0,0 +1,9 @@
1
+ <svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M30.3334 0.333313H3.66671C1.83337 0.333313 0.350041 1.83331 0.350041 3.66665L0.333374 33.6666L7.00004 27H30.3334C32.1667 27 33.6667 25.5 33.6667 23.6666V3.66665C33.6667 1.83331 32.1667 0.333313 30.3334 0.333313ZM12 15.3333H8.66671V12H12V15.3333ZM18.6667 15.3333H15.3334V12H18.6667V15.3333ZM25.3334 15.3333H22V12H25.3334V15.3333Z" fill="url(#paint0_linear_3441_1581)"/>
3
+ <defs>
4
+ <linearGradient id="paint0_linear_3441_1581" x1="0.333374" y1="0.333313" x2="33.6298" y2="33.6667" gradientUnits="userSpaceOnUse">
5
+ <stop stop-color="#2B6B9F"/>
6
+ <stop offset="1" stop-color="#D5FFCB"/>
7
+ </linearGradient>
8
+ </defs>
9
+ </svg>
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.7.14",
4
+ "version": "0.7.16",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -11,6 +11,7 @@ export default StyleSheet.create({
11
11
  justifyContent: 'space-between',
12
12
  borderRadius: 8,
13
13
  marginTop: 16,
14
+ marginBottom: 16,
14
15
  shadowColor: Colors.Gray6,
15
16
  shadowOffset: {
16
17
  width: 0,
@@ -29,13 +29,13 @@ const IFrame = memo(({ item = {} }) => {
29
29
  }, [id, url]);
30
30
 
31
31
  return (
32
- <View>
32
+ <View style={styles.viewIframe}>
33
33
  <TouchableOpacity style={styles.reloadButton} onClick={reload}>
34
34
  <IconComponent size={20} icon="ReloadOutlined" />
35
35
  </TouchableOpacity>
36
36
  <WebView
37
37
  source={{ uri: urlWithEnv }}
38
- style={styles.iframe}
38
+ className={styles.iframe}
39
39
  ref={ref}
40
40
  title={title}
41
41
  javaScriptEnabled
@@ -30,4 +30,9 @@ export const styles = StyleSheet.create({
30
30
  settingWidget: {
31
31
  width: 256,
32
32
  },
33
+ viewIframe: {
34
+ width: '100%',
35
+ display: 'flex',
36
+ height: 200,
37
+ },
33
38
  });
@@ -21,7 +21,8 @@ const API = {
21
21
  AUTOMATE: (id) => `/property_manager/units/${id}/automate/`,
22
22
  DEVICE_CONTROL: (id) => `/property_manager/units/${id}/device_control/`,
23
23
  DEVICE_SENSOR: (id) => `/property_manager/units/${id}/device_sensor/`,
24
- SENSORS_STATUS: (id) => `/property_manager/units/${id}/sensors_status/`,
24
+ END_DEVICES_STATUS: (id) =>
25
+ `/property_manager/units/${id}/end_devices_status/`,
25
26
  CHANGE_OWNER: (id) => `/property_manager/units/${id}/change_owner/`,
26
27
  FAVOURITE_DEVICES: (id) =>
27
28
  `/property_manager/units/${id}/favourite_devices/`,
@@ -107,6 +108,8 @@ const API = {
107
108
  `/property_manager/automate/${automateId}/update_script_action/`,
108
109
  UPDATE_SCRIPT_EMAIL: (automateId) =>
109
110
  `/property_manager/automate/${automateId}/update_script_email/`,
111
+ UPDATE_SCRIPT_SMS: (automateId) =>
112
+ `/property_manager/automate/${automateId}/update_script_sms/`,
110
113
  ADD_SCRIPT_ACTION: (id) =>
111
114
  `/property_manager/automate/${id}/add_script_action/`,
112
115
  ADD_SCRIPT_NOTIFY: (id) =>
@@ -115,7 +118,7 @@ const API = {
115
118
  `/property_manager/automate/${id}/add_script_delay/`,
116
119
  ADD_SCRIPT_EMAIL: (id) =>
117
120
  `/property_manager/automate/${id}/add_script_email/`,
118
-
121
+ ADD_SCRIPT_SMS: (id) => `/property_manager/automate/${id}/add_script_sms/`,
119
122
  FETCH_AUTOMATE: (automateId) => `/property_manager/automate/${automateId}/`,
120
123
  CREATE_AUTOMATE: () => '/property_manager/automate/',
121
124
  UPDATE_AUTOMATE: (automateId) =>
@@ -130,6 +133,8 @@ const API = {
130
133
  GET_MULTI_UNITS: () => '/property_manager/automate/multi_unit/',
131
134
  ACTIVITY_LOG: (id) => `/property_manager/automate/${id}/logs/`,
132
135
  STARRED_SCRIPTS: () => '/property_manager/automate/starred_scripts/',
136
+ ENABLE_LOCAL_CONTROL: (id) =>
137
+ `/property_manager/automate/${id}/enable_local_control/`,
133
138
  },
134
139
  HOME_ASSISTANT: {
135
140
  CHECK_SEND_EMAIL: () =>
@@ -244,6 +249,7 @@ const API = {
244
249
  COUNT: () => '/chip_manager/developer_mode_chips/count/',
245
250
  DETAIL: (id) => `/chip_manager/developer_mode_chips/${id}/`,
246
251
  REBOOT: (id) => `/chip_manager/developer_mode_chips/${id}/reboot_chip/`,
252
+ SHARED: () => '/property_manager/iot_dashboard/filters/chip_shared/',
247
253
  },
248
254
  ARDUINO: {
249
255
  DETAIL: (id) => `/iot/modules/arduino/gateways/${id}/`,
@@ -1,36 +1,51 @@
1
1
  import { useFocusEffect, useNavigation } from '@react-navigation/native';
2
2
  import { useCallback, useRef } from 'react';
3
- import { BackHandler } from 'react-native';
3
+ import { BackHandler, Platform } from 'react-native';
4
4
 
5
5
  export const useBlockBack = (actionBack) => {
6
6
  const navigation = useNavigation();
7
- const isBeforeRemoveHandled = useRef(false);
7
+ const isListening = useRef(false);
8
8
 
9
9
  const blockBack = useCallback(() => {
10
10
  actionBack && actionBack();
11
11
  return true;
12
12
  }, [actionBack]);
13
13
 
14
+ const blockBeforeRemove = useCallback(
15
+ (e) => {
16
+ e.preventDefault();
17
+ blockBack();
18
+ },
19
+ [blockBack]
20
+ );
21
+
14
22
  useFocusEffect(
15
23
  useCallback(() => {
16
- const onBeforeRemove = (e) => {
17
- if (!isBeforeRemoveHandled.current) {
18
- isBeforeRemoveHandled.current = true;
19
- e.preventDefault();
20
- blockBack();
21
- }
22
- };
23
-
24
- BackHandler.addEventListener('hardwareBackPress', blockBack);
25
- const unsubscribe = navigation.addListener(
26
- 'beforeRemove',
27
- onBeforeRemove
28
- );
29
-
30
- return () => {
31
- BackHandler.removeEventListener('hardwareBackPress', blockBack);
32
- unsubscribe();
33
- };
34
- }, [blockBack, navigation])
24
+ if (isListening.current) {
25
+ return;
26
+ }
27
+ isListening.current = true;
28
+
29
+ if (Platform.OS === 'ios') {
30
+ const unsubscribe = navigation.addListener(
31
+ 'beforeRemove',
32
+ blockBeforeRemove
33
+ );
34
+
35
+ return () => {
36
+ unsubscribe();
37
+ isListening.current = false;
38
+ };
39
+ }
40
+
41
+ if (Platform.OS === 'android') {
42
+ BackHandler.addEventListener('hardwareBackPress', blockBack);
43
+
44
+ return () => {
45
+ BackHandler.removeEventListener('hardwareBackPress', blockBack);
46
+ isListening.current = false;
47
+ };
48
+ }
49
+ }, [blockBack, blockBeforeRemove, navigation])
35
50
  );
36
51
  };
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useContext, useEffect, useRef } from 'react';
2
2
  import { useIsFocused } from '@react-navigation/native';
3
3
  import { SCContext, useSCContextSelector } from '../../context';
4
- import { axiosGet } from '../../utils/Apis/axios';
4
+ import { axiosPost } from '../../utils/Apis/axios';
5
5
  import { API } from '../../configs';
6
6
  import { Action } from '../../context/actionType';
7
7
 
@@ -13,7 +13,7 @@ const useDevicesStatus = (unit, devices) => {
13
13
  (state) => state.app.isNetworkConnected
14
14
  );
15
15
  const isFocused = useIsFocused();
16
- const hasFetched = useRef(false); // Track if data has been fetched
16
+ const hasFetched = useRef([]); // Track if data has been fetched
17
17
 
18
18
  const getDevicesStatus = useCallback(
19
19
  async (_unit, _devices) => {
@@ -21,24 +21,29 @@ const useDevicesStatus = (unit, devices) => {
21
21
  clearTimeout(timeoutId);
22
22
  timeoutId = null;
23
23
  }
24
- const params = new URLSearchParams();
25
- _devices.forEach((device) => {
26
- params.append('sensors', device.id);
27
- });
28
- const { success, data } = await axiosGet(
29
- API.UNIT.SENSORS_STATUS(_unit.id),
24
+ const end_device_ids = _devices.map((item) => item.id);
25
+
26
+ if (hasFetched.current.toString() === end_device_ids.toString()) {
27
+ return;
28
+ }
29
+ const { success, data } = await axiosPost(
30
+ API.UNIT.END_DEVICES_STATUS(_unit.id),
30
31
  {
31
- params: params,
32
+ end_device_ids,
32
33
  }
33
34
  );
34
- success && setAction(Action.SET_DEVICES_STATUS, data);
35
+
36
+ if (success) {
37
+ hasFetched.current = end_device_ids;
38
+ setAction(Action.SET_DEVICES_STATUS, data);
39
+ }
35
40
  timeoutId = setTimeout(() => getDevicesStatus(_unit, _devices), 10000);
36
41
  },
37
42
  [setAction]
38
43
  );
39
44
 
40
45
  useEffect(() => {
41
- if (!isFocused || !isNetworkConnected || hasFetched.current) {
46
+ if (!isFocused || !isNetworkConnected) {
42
47
  return;
43
48
  }
44
49
  if (!devices?.length) {
@@ -53,7 +58,6 @@ const useDevicesStatus = (unit, devices) => {
53
58
  }
54
59
 
55
60
  getDevicesStatus(unit, devices);
56
- hasFetched.current = true; // Mark as fetched
57
61
  return () => {
58
62
  if (timeoutId) {
59
63
  clearTimeout(timeoutId);
@@ -65,7 +69,6 @@ const useDevicesStatus = (unit, devices) => {
65
69
  useEffect(() => {
66
70
  // Reset the hasFetched flag when the component is no longer focused
67
71
  if (!isFocused) {
68
- hasFetched.current = false;
69
72
  if (timeoutId) {
70
73
  clearTimeout(timeoutId); // Clear timeout when losing focus
71
74
  timeoutId = null;
@@ -72,6 +72,9 @@ import { HanetCameraStack } from './HanetCameraStack';
72
72
  import { styles } from './UnitStackStyles';
73
73
  import { bleManager } from '../utils/bluetooth';
74
74
  import UpdateReceiverEmailScript from '../screens/Automate/EditActionsList/UpdateReceiverEmailScript';
75
+ import SetupScriptSms from '../screens/Automate/AddNewAction/SetupScriptSms';
76
+ import SetupScriptReceiverSms from '../screens/Automate/AddNewAction/SetupScriptReceiverSms';
77
+ import UpdateReceiverSmsScript from '../screens/Automate/EditActionsList/UpdateReceiverSmsScript';
75
78
 
76
79
  const Stack = createNativeStackNavigator();
77
80
 
@@ -446,6 +449,27 @@ export const UnitStack = memo((props) => {
446
449
  headerShown: false,
447
450
  }}
448
451
  />
452
+ <Stack.Screen
453
+ name={Route.SetupScriptSms}
454
+ component={SetupScriptSms}
455
+ options={{
456
+ headerShown: false,
457
+ }}
458
+ />
459
+ <Stack.Screen
460
+ name={Route.SetupScriptReceiverSms}
461
+ component={SetupScriptReceiverSms}
462
+ options={{
463
+ headerShown: false,
464
+ }}
465
+ />
466
+ <Stack.Screen
467
+ name={Route.UpdateReceiverSmsScript}
468
+ component={UpdateReceiverSmsScript}
469
+ options={{
470
+ headerShown: false,
471
+ }}
472
+ />
449
473
  <Stack.Screen
450
474
  name={Route.AddAutomationTypeSmart}
451
475
  component={AddAutomationTypeSmart}
@@ -221,23 +221,21 @@ const ScanDeviceLocal = ({ route }) => {
221
221
  title={t('device_scaned')}
222
222
  isShowSeparator
223
223
  />
224
- {!deviceList.length ? (
224
+
225
+ <View style={styles.rowContainer}>
226
+ <Text style={styles.subTitle} type="Body">
227
+ {t('select_device_and_connect')}
228
+ </Text>
225
229
  <ActivityIndicator style={styles.containerLoading} />
226
- ) : (
227
- <>
228
- <Text style={styles.subTitle} type="Body">
229
- {t('select_device_and_connect')}
230
- </Text>
231
- <FlatList
232
- style={styles.listContainer}
233
- keyExtractor={(item) => item?.host}
234
- data={deviceList}
235
- renderItem={renderItem}
236
- extraData={deviceList}
237
- numColumns={1}
238
- />
239
- </>
240
- )}
230
+ </View>
231
+ <FlatList
232
+ style={styles.listContainer}
233
+ keyExtractor={(item) => item?.host}
234
+ data={deviceList}
235
+ renderItem={renderItem}
236
+ extraData={deviceList}
237
+ numColumns={1}
238
+ />
241
239
 
242
240
  <ViewButtonBottom
243
241
  leftTitle={t('cancel')}
@@ -6,6 +6,10 @@ export default StyleSheet.create({
6
6
  flex: 1,
7
7
  backgroundColor: Colors.Gray2,
8
8
  },
9
+ rowContainer: {
10
+ flexDirection: 'row',
11
+ alignItems: 'center',
12
+ },
9
13
  title: {
10
14
  marginVertical: 16,
11
15
  marginLeft: 16,
@@ -53,6 +57,7 @@ export default StyleSheet.create({
53
57
  containerLoading: {
54
58
  flex: 1,
55
59
  justifyContent: 'center',
56
- alignItems: 'center',
60
+ alignItems: 'flex-start',
61
+ marginLeft: 16,
57
62
  },
58
63
  });
@@ -105,6 +105,9 @@ describe('test ScanDeviceLocal', () => {
105
105
  );
106
106
  expect(zeroconfMock.on).toHaveBeenCalledWith('error', expect.any(Function));
107
107
  expect(zeroconfMock.scan).toHaveBeenCalledWith('plugandplay', 'tcp');
108
+
109
+ const activityIndicator = instance.findByType(ActivityIndicator);
110
+ expect(activityIndicator).toBeTruthy();
108
111
  };
109
112
 
110
113
  it('test connect gateway and navigate', async () => {
@@ -382,17 +385,6 @@ describe('test ScanDeviceLocal', () => {
382
385
  expect(mockedGoBack).toHaveBeenCalled();
383
386
  });
384
387
 
385
- it('test renders loading indicator when device list is empty', async () => {
386
- const route = { params: { unit: 1 } };
387
- await act(async () => {
388
- tree = await renderer.create(wrapComponent(route));
389
- });
390
- const instance = tree.root;
391
-
392
- const activityIndicator = instance.findByType(ActivityIndicator);
393
- expect(activityIndicator).toBeTruthy();
394
- });
395
-
396
388
  it('test renders device list when devices are available', async () => {
397
389
  const route = { params: { unit: 1 } };
398
390
  await act(async () => {
@@ -451,8 +443,7 @@ describe('test ScanDeviceLocal', () => {
451
443
  await removeCallback('Device 1');
452
444
  });
453
445
 
454
- const flatListAfter = instance.findAllByType(FlatList);
455
- expect(flatListAfter.length).toBe(0);
446
+ expect(flatList.props.data).toEqual([]);
456
447
 
457
448
  const activityIndicator = instance.findByType(ActivityIndicator);
458
449
  expect(activityIndicator).toBeTruthy();
@@ -0,0 +1,167 @@
1
+ import React, { useCallback, useMemo, useState, useEffect, memo } from 'react';
2
+ import { FlatList, View } from 'react-native';
3
+ import { useNavigation } from '@react-navigation/native';
4
+ import styles from './Styles/SetupScriptReceiverEmailStyles';
5
+ import { CircleView, HeaderCustom, Text } from '../../../commons';
6
+ import { useTranslations } from '../../../hooks/Common/useTranslations';
7
+
8
+ import BottomButtonView from '../../../commons/BottomButtonView';
9
+ import { axiosPost, axiosGet } from '../../../utils/Apis/axios';
10
+ import { API, Colors } from '../../../configs';
11
+ import { ToastBottomHelper } from '../../../utils/Utils';
12
+ import Routes from '../../../utils/Route';
13
+ import moment from 'moment';
14
+ import CheckBox from '@react-native-community/checkbox';
15
+ import { useSCContextSelector } from '../../../context';
16
+ import { Image } from 'react-native';
17
+
18
+ const SetupScriptReceiverSms = ({ route }) => {
19
+ const t = useTranslations();
20
+ const { goBack, navigate } = useNavigation();
21
+ const { automate = {}, unitId, formData } = route.params || {};
22
+ const { id: automateId } = automate;
23
+ const [members, setMembers] = useState([]);
24
+ const [listUser, setListUser] = useState([]);
25
+ const currentUserId = useSCContextSelector(
26
+ (state) => state.auth.account.user.id
27
+ );
28
+
29
+ const loadMembers = useCallback(async () => {
30
+ const { success, data } = await axiosGet(API.SHARE.UNITS_MEMBERS(unitId));
31
+ if (success) {
32
+ setMembers(data);
33
+ }
34
+ }, [unitId]);
35
+
36
+ useEffect(() => {
37
+ loadMembers();
38
+ }, [loadMembers]);
39
+
40
+ const onNext = useCallback(async () => {
41
+ formData.receiver = listUser;
42
+ const { success } = await axiosPost(
43
+ API.AUTOMATE.ADD_SCRIPT_SMS(automateId),
44
+ formData
45
+ );
46
+ if (success) {
47
+ ToastBottomHelper.success(t('text_done'));
48
+ navigate({
49
+ name: Routes.ScriptDetail,
50
+ merge: true,
51
+ params: { saveAt: moment().valueOf() },
52
+ });
53
+ } else {
54
+ ToastBottomHelper.error(t('error_please_try_later'));
55
+ }
56
+ }, [automateId, formData, listUser, navigate, t]);
57
+
58
+ const canSave = useMemo(() => {
59
+ const { message } = formData;
60
+ return !!message && !!listUser.length;
61
+ }, [formData, listUser.length]);
62
+
63
+ const arrColor = useMemo(
64
+ () => [
65
+ Colors.GeekBlue3,
66
+ Colors.Purple3,
67
+ Colors.Orange3,
68
+ Colors.Volcano3,
69
+ Colors.Blue9,
70
+ Colors.Green3,
71
+ Colors.Cyan2,
72
+ ],
73
+ []
74
+ );
75
+
76
+ const onChecked = useCallback(
77
+ (id) => (checked) => {
78
+ setListUser((prevListUser) =>
79
+ checked
80
+ ? [...prevListUser, id]
81
+ : prevListUser.filter((userId) => userId !== id)
82
+ );
83
+ },
84
+ []
85
+ );
86
+
87
+ const RowMember = memo(({ member, index, onValueChange }) => {
88
+ const { id, name, avatar, share_id, phone_number } = member;
89
+ const [role, roleColor] = useMemo(() => {
90
+ if (!share_id) {
91
+ return [t('owner'), Colors.Primary];
92
+ }
93
+ if (id === currentUserId) {
94
+ return [t('me'), Colors.Primary];
95
+ }
96
+ return [t('member'), Colors.Gray6];
97
+ }, [share_id, id]);
98
+
99
+ const firstWordsInName = useMemo(() => {
100
+ return name.charAt();
101
+ }, [name]);
102
+
103
+ const circleColor = arrColor[index % arrColor.length];
104
+
105
+ return (
106
+ <View style={styles.rowContainer}>
107
+ <View style={styles.border}>
108
+ <CheckBox
109
+ disabled={!phone_number}
110
+ lineWidth={4}
111
+ value={listUser.includes(id)}
112
+ onValueChange={onValueChange(id)}
113
+ style={styles.checkbox}
114
+ />
115
+ <View style={styles.paddingLeft16}>
116
+ {avatar ? (
117
+ <Image source={{ uri: avatar }} style={styles.avatar} />
118
+ ) : (
119
+ <CircleView size={40} backgroundColor={circleColor} center>
120
+ <Text color={Colors.White}>{firstWordsInName}</Text>
121
+ </CircleView>
122
+ )}
123
+ </View>
124
+ <View style={styles.paddingLeft16}>
125
+ <Text style={styles.titleName}>{name}</Text>
126
+ {phone_number ? (
127
+ <Text style={styles.status}>{phone_number}</Text>
128
+ ) : (
129
+ <Text style={styles.invalid}>{t('no_phone_number')}</Text>
130
+ )}
131
+ </View>
132
+ <View style={styles.endFlex}>
133
+ <Text style={[styles.textRole, { color: roleColor }]}>{role}</Text>
134
+ </View>
135
+ </View>
136
+ </View>
137
+ );
138
+ });
139
+
140
+ return (
141
+ <View style={styles.wrap}>
142
+ <HeaderCustom isShowClose onClose={goBack} title={t('sms_to')} />
143
+ <FlatList
144
+ data={members}
145
+ renderItem={({ item, index }) => (
146
+ <RowMember member={item} index={index} onValueChange={onChecked} />
147
+ )}
148
+ keyExtractor={(item) => item.id.toString()}
149
+ ListEmptyComponent={
150
+ <View style={styles.viewEmpty}>
151
+ <Text style={styles.textCenter}>{t('no_member')}</Text>
152
+ </View>
153
+ }
154
+ />
155
+ <View style={styles.container}>
156
+ <BottomButtonView
157
+ style={styles.bottomButtonView}
158
+ mainTitle={t('done')}
159
+ onPressMain={onNext}
160
+ typeMain={canSave ? 'primary' : 'disabled'}
161
+ />
162
+ </View>
163
+ </View>
164
+ );
165
+ };
166
+
167
+ export default SetupScriptReceiverSms;
@@ -0,0 +1,73 @@
1
+ import React, { useCallback, useMemo, useState } from 'react';
2
+ import { Keyboard, TouchableWithoutFeedback, View } from 'react-native';
3
+ import { useNavigation } from '@react-navigation/native';
4
+ import styles from './Styles/SetupScriptEmailStyles';
5
+ import { HeaderCustom, Text } from '../../../commons';
6
+ import { useTranslations } from '../../../hooks/Common/useTranslations';
7
+
8
+ import _TextInput from '../../../commons/Form/TextInput';
9
+ import AccessibilityLabel from '../../../configs/AccessibilityLabel';
10
+ import BottomButtonView from '../../../commons/BottomButtonView';
11
+ import Routes from '../../../utils/Route';
12
+ import { Colors } from '../../../configs';
13
+
14
+ const SetupScriptSms = ({ route }) => {
15
+ const t = useTranslations();
16
+ const { goBack, navigate } = useNavigation();
17
+ const { automate, unitId, multiUnit } = route.params || {};
18
+ const initialUnitId = useMemo(
19
+ () => unitId || multiUnit.id,
20
+ [unitId, multiUnit?.id]
21
+ );
22
+ const [formData, setFormData] = useState({ unit: initialUnitId });
23
+
24
+ const onChangeMessage = (value) => {
25
+ setFormData((state) => ({
26
+ ...state,
27
+ message: value,
28
+ }));
29
+ };
30
+
31
+ const onNext = useCallback(async () => {
32
+ navigate(Routes.SetupScriptReceiverSms, {
33
+ automate,
34
+ unitId: initialUnitId,
35
+ formData,
36
+ });
37
+ }, [navigate, automate, initialUnitId, formData]);
38
+
39
+ const canSave = useMemo(() => {
40
+ const { message } = formData || {};
41
+ return !!message;
42
+ }, [formData]);
43
+
44
+ return (
45
+ <View style={styles.wrap}>
46
+ <HeaderCustom isShowClose onClose={goBack} title={t('sms_content')} />
47
+ <TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
48
+ <View style={styles.container}>
49
+ <_TextInput
50
+ placeholder={t('message_sms')}
51
+ onChange={onChangeMessage}
52
+ textInputStyle={styles.textMessage}
53
+ value={formData?.message}
54
+ accessibilityLabel={AccessibilityLabel.AUTOMATE_MESSAGE_NOTIFY}
55
+ multiline={true}
56
+ maxLength={255}
57
+ />
58
+ <View style={styles.textWarning}>
59
+ <Text color={Colors.Gray}>{t('only_in_local_control')}</Text>
60
+ </View>
61
+ <BottomButtonView
62
+ style={styles.bottomButtonView}
63
+ mainTitle={t('next')}
64
+ onPressMain={onNext}
65
+ typeMain={canSave ? 'primary' : 'disabled'}
66
+ />
67
+ </View>
68
+ </TouchableWithoutFeedback>
69
+ </View>
70
+ );
71
+ };
72
+
73
+ export default SetupScriptSms;
@@ -27,6 +27,11 @@ export default StyleSheet.create({
27
27
  borderStyle: 'solid',
28
28
  borderRadius: 10,
29
29
  },
30
+ textWarning: {
31
+ marginTop: 5,
32
+ justifyContent: 'center',
33
+ alignItems: 'center',
34
+ },
30
35
  bottomButtonView: {
31
36
  paddingTop: 24,
32
37
  paddingBottom: 32,