@eohjsc/react-native-smart-city 0.3.39 → 0.3.40

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eohjsc/react-native-smart-city",
3
3
  "title": "React Native Smart Home",
4
- "version": "0.3.39",
4
+ "version": "0.3.40",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -0,0 +1,19 @@
1
+ import React, { memo } from 'react';
2
+ import { View } from 'react-native';
3
+
4
+ import Text from '../Text';
5
+ import styles from './styles';
6
+ import { useTranslations } from '../../hooks/Common/useTranslations';
7
+
8
+ const StatusBox = ({ status }) => {
9
+ const t = useTranslations();
10
+ return (
11
+ <View style={[styles.viewStatus, status && styles.viewOnline]}>
12
+ <Text style={[styles.textStatus, status && styles.textOnline]}>
13
+ {t(status ? 'online' : 'offline')}
14
+ </Text>
15
+ </View>
16
+ );
17
+ };
18
+
19
+ export default memo(StatusBox);
@@ -0,0 +1,30 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { Colors } from '../../configs';
3
+
4
+ export default StyleSheet.create({
5
+ viewStatus: {
6
+ width: 65,
7
+ paddingHorizontal: 8,
8
+ paddingVertical: 4,
9
+ borderRadius: 54,
10
+ backgroundColor: Colors.White,
11
+ borderColor: Colors.Neutral.Neutral3,
12
+ borderWidth: 1,
13
+ },
14
+ textStatus: {
15
+ fontStyle: 'normal',
16
+ fontWeight: 600,
17
+ letterSpacing: 0.04,
18
+ fontSize: 12,
19
+ lineHeight: 16,
20
+ textTransform: 'uppercase',
21
+ textAlign: 'center',
22
+ },
23
+ textOnline: {
24
+ fontStyle: 'bold',
25
+ },
26
+ viewOnline: {
27
+ backgroundColor: Colors.LightGreen,
28
+ borderWidth: 0,
29
+ },
30
+ });
@@ -16,7 +16,10 @@ import RadioCircle from './RadioCircle';
16
16
  import { CircleButton } from './CircleButton';
17
17
  import HorizontalPicker from './HorizontalPicker';
18
18
  import FullLoading from './FullLoading';
19
-
19
+ import Text from './Text';
20
+ import { HeaderCustom } from './Header';
21
+ import StatusBox from './StatusBox';
22
+ import WrapHeaderScrollable from './Sharing/WrapHeaderScrollable';
20
23
  export {
21
24
  ButtonPopup,
22
25
  CircleView,
@@ -36,4 +39,8 @@ export {
36
39
  CircleButton,
37
40
  HorizontalPicker,
38
41
  FullLoading,
42
+ Text,
43
+ HeaderCustom,
44
+ StatusBox,
45
+ WrapHeaderScrollable,
39
46
  };
@@ -211,6 +211,10 @@ const API = {
211
211
  GET_TEMPLATES: '/property_manager/iot_dashboard/dev_mode/templates/',
212
212
  GET_WIDGETS: (templateId) =>
213
213
  `/property_manager/iot_dashboard/dev_mode/templates/${templateId}/list_widgets/`,
214
+ GATEWAY: {
215
+ LIST: () => '/chip_manager/developer_mode_chips/',
216
+ DETAIL: (id) => `/chip_manager/developer_mode_chips/${id}/`,
217
+ },
214
218
  },
215
219
  };
216
220
 
@@ -638,7 +638,8 @@ export default {
638
638
  BUTTON_CANCEL_EDIT_ACTION_LIST: 'BUTTON_CANCEL_EDIT_ACTION_LIST',
639
639
  BUTTON_SAVE_EDIT_ACTION_LIST: 'BUTTON_SAVE_EDIT_ACTION_LIST',
640
640
  BUTTON_REMOVE_EDIT_ACTION_LIST: 'BUTTON_REMOVE_EDIT_ACTION_LIST',
641
-
641
+ // GATEWAY
642
+ LIST_GATEWAY: 'LIST_GATEWAY',
642
643
  //Notification
643
644
  CUSTOM_TEXT: 'CUSTOM_TEXT',
644
645
  //ButtonWrapper
@@ -60,6 +60,7 @@ export const Colors = {
60
60
  Gray17: '#D3D3D3',
61
61
  Gray18: '#9B9A9B',
62
62
  Gray19: '#E5E5E5',
63
+ Gray20: '#808080',
63
64
 
64
65
  // Range Green
65
66
  Green1: '#F6FFED',
@@ -220,6 +220,15 @@ const DeviceDetail = ({ route }) => {
220
220
  }
221
221
  }, [fetchSensorDetail, sensorId, sensorData]);
222
222
 
223
+ const fetchRemoteControlOptions = useCallback(async () => {
224
+ const { success, data } = await axiosGet(
225
+ API.DEVICE.REMOTE_CONTROL_OPTIONS(sensor?.id),
226
+ {},
227
+ true
228
+ );
229
+ success && setControlOptions(data);
230
+ }, [sensor]);
231
+
223
232
  const fetchDataDeviceDetail = useCallback(async () => {
224
233
  if (!token) {
225
234
  return;
@@ -259,16 +268,11 @@ const DeviceDetail = ({ route }) => {
259
268
  }
260
269
  setLoading((preState) => ({ ...preState, displayTemplate: false }));
261
270
 
262
- const controlResult = await axiosGet(
263
- API.DEVICE.REMOTE_CONTROL_OPTIONS(sensor?.id),
264
- {},
265
- true
266
- );
267
-
268
- if (controlResult.success) {
269
- setControlOptions(controlResult.data);
270
- }
271
- }, [token, sensor, setDeviceId, setLastEvent]);
271
+ success &&
272
+ data.items.length &&
273
+ data.items.some((item) => item.type === 'action') &&
274
+ (await fetchRemoteControlOptions());
275
+ }, [token, sensor, setDeviceId, fetchRemoteControlOptions]);
272
276
 
273
277
  const {
274
278
  deviceId: emergencyDeviceId,
@@ -1,16 +1,43 @@
1
1
  import React from 'react';
2
- import { View } from 'react-native';
2
+ import { FlatList } from 'react-native';
3
3
  import { act, create } from 'react-test-renderer';
4
4
  import Gateway from '..';
5
+ import { SCProvider } from '../../../context';
6
+ import { mockSCStore } from '../../../context/mockStore';
7
+ import { AccessibilityLabel } from '../../../configs/Constants';
5
8
 
6
- describe('Test Template screen', () => {
9
+ const wrapComponent = (route) => (
10
+ <SCProvider initState={mockSCStore({})}>
11
+ <Gateway />
12
+ </SCProvider>
13
+ );
14
+
15
+ describe('Test Gateway screen', () => {
7
16
  let tree;
8
17
  it('Test render', async () => {
9
18
  await act(async () => {
10
- tree = await create(<Gateway />);
19
+ tree = await create(wrapComponent());
20
+ });
21
+ const instance = tree.root;
22
+ const view = instance.findByProps({
23
+ accessibilityLabel: AccessibilityLabel.LIST_GATEWAY,
24
+ });
25
+ const flatLists = instance.findAllByType(FlatList);
26
+ expect(view.props.children[0].props.children).toEqual('Gateway');
27
+ expect(flatLists).toHaveLength(1);
28
+ });
29
+
30
+ it('Test render empty', async () => {
31
+ await act(async () => {
32
+ tree = await create(wrapComponent());
11
33
  });
12
34
  const instance = tree.root;
13
- const Views = instance.findAllByType(View);
14
- expect(Views).toHaveLength(1);
35
+ const flatLists = instance.findAllByType(FlatList);
36
+ expect(
37
+ flatLists[0].props.ListEmptyComponent.props.children.props.text1
38
+ ).toEqual('No Gateway yet');
39
+ expect(
40
+ flatLists[0].props.ListEmptyComponent.props.children.props.text2
41
+ ).toEqual("You don't have any gateway");
15
42
  });
16
43
  });
@@ -0,0 +1,83 @@
1
+ import React from 'react';
2
+ import { create, act } from 'react-test-renderer';
3
+ import { TouchableOpacity } from 'react-native';
4
+ import { SCProvider } from '../../../../../context';
5
+ import { mockSCStore } from '../../../../../context/mockStore';
6
+ import GatewayItem from '..';
7
+
8
+ const mockOnPressItem = jest.fn();
9
+
10
+ const wrapComponent = (item) => (
11
+ <SCProvider initState={mockSCStore({})}>
12
+ <GatewayItem item={item} onPress={mockOnPressItem} />
13
+ </SCProvider>
14
+ );
15
+
16
+ const mockNavigate = jest.fn();
17
+ jest.mock('@react-navigation/native', () => {
18
+ return {
19
+ ...jest.requireActual('@react-navigation/native'),
20
+ useNavigation: () => ({
21
+ navigate: mockNavigate,
22
+ }),
23
+ useIsFocused: jest.fn(),
24
+ };
25
+ });
26
+
27
+ describe('Test GatewayItem', () => {
28
+ let tree;
29
+ let item;
30
+ beforeEach(() => {
31
+ item = {
32
+ id: 1,
33
+ name: 'gatewayItem',
34
+ is_connected: false,
35
+ last_healthy: 1,
36
+ };
37
+ mockNavigate.mockClear();
38
+ });
39
+
40
+ it('test render GatewayItem connect false', async () => {
41
+ await act(async () => {
42
+ tree = await create(wrapComponent(item));
43
+ });
44
+ const instance = tree.root;
45
+
46
+ const buttons = instance.findAllByType(TouchableOpacity);
47
+ expect(buttons).toHaveLength(1);
48
+ });
49
+
50
+ it('test render GatewayItem connect true', async () => {
51
+ item = {
52
+ ...item,
53
+ is_connected: true,
54
+ };
55
+ await act(async () => {
56
+ tree = await create(wrapComponent(item));
57
+ });
58
+ const instance = tree.root;
59
+
60
+ const buttons = instance.findAllByType(TouchableOpacity);
61
+ expect(buttons).toHaveLength(1);
62
+ });
63
+
64
+ it('test render GatewayItem onPress', async () => {
65
+ item = {
66
+ id: 1,
67
+ name: 'gatewayItem',
68
+ is_connected: false,
69
+ last_healthy: 1,
70
+ };
71
+ await act(async () => {
72
+ tree = await create(wrapComponent(item));
73
+ });
74
+ const instance = tree.root;
75
+
76
+ const buttons = instance.findAllByType(TouchableOpacity);
77
+ expect(buttons).toHaveLength(1);
78
+ await act(async () => {
79
+ await buttons[0].props.onPress();
80
+ });
81
+ expect(mockOnPressItem).toHaveBeenCalled();
82
+ });
83
+ });
@@ -0,0 +1,27 @@
1
+ import React, { memo } from 'react';
2
+ import { TouchableOpacity, View } from 'react-native';
3
+ import { Colors } from '../../../../configs';
4
+ import { Text, StatusBox } from '../../../../commons';
5
+ import styles from './styles';
6
+
7
+ const GatewayItem = ({ item, onPress }) => {
8
+ return (
9
+ <View style={styles.wrap}>
10
+ <TouchableOpacity style={styles.button} onPress={onPress}>
11
+ <View style={styles.viewTitle}>
12
+ <Text type="Body" bold color={Colors.Gray9} style={styles.textTitle}>
13
+ {item?.name}
14
+ </Text>
15
+ </View>
16
+ <View style={styles.viewValue}>
17
+ <Text type="Body" color={Colors.Gray20} style={styles.textValue}>
18
+ {`${item?.last_healthy || '--'} dBm`}
19
+ </Text>
20
+ </View>
21
+ <StatusBox status={item?.is_connected} />
22
+ </TouchableOpacity>
23
+ </View>
24
+ );
25
+ };
26
+
27
+ export default memo(GatewayItem);
@@ -0,0 +1,62 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { Colors, Constants } from '../../../../configs';
3
+
4
+ export default StyleSheet.create({
5
+ wrap: {
6
+ padding: 16,
7
+ marginBottom: 16,
8
+ backgroundColor: Colors.White,
9
+ borderRadius: 8,
10
+ width: Constants.width / 2 - 24,
11
+ borderWidth: 1,
12
+ borderColor: Colors.Neutral.Neutral3,
13
+ },
14
+ button: {
15
+ flex: 1,
16
+ justifyContent: 'space-between',
17
+ },
18
+ viewTitle: {
19
+ marginBottom: 6,
20
+ },
21
+ viewValue: {
22
+ marginBottom: 6,
23
+ },
24
+ textTitle: {
25
+ lineHeight: 20,
26
+ height: 40,
27
+ fontWeight: 600,
28
+ textOverflow: 'ellipsis',
29
+ },
30
+ textValue: {
31
+ fontWeight: 400,
32
+ },
33
+ viewStatus: {
34
+ width: 65,
35
+ paddingHorizontal: 8,
36
+ paddingVertical: 4,
37
+ borderRadius: 54,
38
+ backgroundColor: Colors.White,
39
+ borderColor: Colors.Neutral.Neutral3,
40
+ borderWidth: 1,
41
+ },
42
+ textStatus: {
43
+ fontStyle: 'normal',
44
+ fontWeight: 600,
45
+ letterSpacing: 0.04,
46
+ fontSize: 12,
47
+ lineHeight: 16,
48
+ textTransform: 'uppercase',
49
+ textAlign: 'center',
50
+ },
51
+ listGateways: {
52
+ width: '100%',
53
+ paddingVertical: 16,
54
+ },
55
+ textOnline: {
56
+ fontStyle: 'bold',
57
+ },
58
+ viewOnline: {
59
+ backgroundColor: Colors.LightGreen,
60
+ borderWidth: 0,
61
+ },
62
+ });
@@ -0,0 +1,38 @@
1
+ import { useCallback, useState } from 'react';
2
+ import { axiosGet } from '../../../utils/Apis/axios';
3
+ import API from '../../../configs/API';
4
+
5
+ export default () => {
6
+ const [loading, setLoading] = useState(true);
7
+ const [gateways, setGateways] = useState([]);
8
+ const [nameSearch, setNameSearch] = useState('');
9
+ const [gatewayDetail, setGatewayDetail] = useState([]);
10
+
11
+ const fetchDataDetail = useCallback(async () => {
12
+ setLoading(true);
13
+ const { success, data } = await axiosGet(API.DEV_MODE.GATEWAY.LIST());
14
+ if (success) {
15
+ setGatewayDetail(data?.results || []);
16
+ }
17
+ setLoading(false);
18
+ }, []);
19
+
20
+ const fetchDataGateways = useCallback(async () => {
21
+ setLoading(true);
22
+ const { success, data } = await axiosGet(API.DEV_MODE.GATEWAY.LIST());
23
+ if (success) {
24
+ setGateways(data?.results || []);
25
+ }
26
+ setLoading(false);
27
+ }, []);
28
+
29
+ return {
30
+ gateways,
31
+ fetchDataGateways,
32
+ loading,
33
+ setNameSearch,
34
+ nameSearch,
35
+ gatewayDetail,
36
+ fetchDataDetail,
37
+ };
38
+ };
@@ -1,8 +1,80 @@
1
- import React from 'react';
2
- import { View } from 'react-native';
1
+ import React, { useCallback, memo, useEffect, useMemo } from 'react';
2
+ import { View, FlatList } from 'react-native';
3
+ import { useNavigation, useIsFocused } from '@react-navigation/native';
4
+ import { AccessibilityLabel } from '../../configs/Constants';
5
+ import { Colors } from '../../configs';
6
+ import { useTranslations } from '../../hooks/Common/useTranslations';
7
+ import { Text } from '../../commons';
8
+ import styles from './styles';
9
+ import { Search, EmptyComponent } from '../../commons/DevMode';
10
+ import GatewayItem from './components/GatewayItem';
11
+ import useGateway from './hooks/useGateway';
12
+ import Routes from '../../utils/Route';
3
13
 
4
- const Gateway = () => {
5
- return <View />;
14
+ const Gateway = ({ route }) => {
15
+ const t = useTranslations();
16
+ const isFocused = useIsFocused();
17
+ const { navigate } = useNavigation();
18
+ const { gateways, fetchDataGateways, setNameSearch, nameSearch } =
19
+ useGateway();
20
+
21
+ const goGoDetail = useCallback(
22
+ (item) => () => {
23
+ navigate(Routes.GatewayDetail, { item });
24
+ },
25
+ [navigate]
26
+ );
27
+
28
+ const gatewaysSearched = useMemo(() => {
29
+ return gateways.filter(
30
+ (item) =>
31
+ item?.name?.toLowerCase()?.indexOf(nameSearch?.toLowerCase()) > -1
32
+ );
33
+ }, [gateways, nameSearch]);
34
+
35
+ const renderListEmptyComponent = useMemo(() => {
36
+ return (
37
+ <View style={styles.wrapEmpty}>
38
+ <EmptyComponent
39
+ text1={t('no_gateway_yet')}
40
+ text2={t('you_dont_have_any_gateway')}
41
+ />
42
+ </View>
43
+ );
44
+ }, [t]);
45
+
46
+ const renderItem = useCallback(
47
+ ({ item, index }) => (
48
+ <GatewayItem item={item} key={index} onPress={goGoDetail(item)} />
49
+ ),
50
+ [goGoDetail]
51
+ );
52
+
53
+ useEffect(() => {
54
+ isFocused && fetchDataGateways();
55
+ }, [isFocused, fetchDataGateways]);
56
+
57
+ return (
58
+ <View
59
+ style={styles.wrap}
60
+ accessibilityLabel={AccessibilityLabel.LIST_GATEWAY}
61
+ >
62
+ <Text type="H3" bold color={Colors.Gray9} style={styles.textTitle}>
63
+ {t('gateway')}
64
+ </Text>
65
+ <Search onSearch={setNameSearch} />
66
+ <FlatList
67
+ columnWrapperStyle={styles.spaceBetween}
68
+ contentContainerStyle={styles.contentContainerStyle}
69
+ keyExtractor={(item) => item?.id}
70
+ data={gatewaysSearched}
71
+ renderItem={renderItem}
72
+ extraData={gatewaysSearched}
73
+ ListEmptyComponent={renderListEmptyComponent}
74
+ numColumns={2}
75
+ />
76
+ </View>
77
+ );
6
78
  };
7
79
 
8
- export default Gateway;
80
+ export default memo(Gateway);
@@ -0,0 +1,32 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import { Colors, Constants } from '../../configs';
3
+
4
+ export default StyleSheet.create({
5
+ wrap: {
6
+ flex: 1,
7
+ paddingTop: 16,
8
+ paddingHorizontal: 16,
9
+ backgroundColor: Colors.Gray2,
10
+ },
11
+ textTitle: {
12
+ fontStyle: 'normal',
13
+ fontWeight: 650,
14
+ fontSize: 28,
15
+ lineHeight: 36,
16
+ marignBottom: 16,
17
+ },
18
+ scrollview: {
19
+ flex: 1,
20
+ },
21
+ contentContainerStyle: {
22
+ flexDirection: 'column',
23
+ },
24
+ wrapEmpty: {
25
+ justifyContent: 'center',
26
+ alignItems: 'center',
27
+ height: Constants.height - 330,
28
+ },
29
+ spaceBetween: {
30
+ justifyContent: 'space-between',
31
+ },
32
+ });
@@ -999,6 +999,10 @@
999
999
  "no_template_yet": "No Template yet",
1000
1000
  "add_your_template": "Add your template at web app",
1001
1001
  "edit_template": "Edit template",
1002
+ "no_gateway_yet": "No Gateway yet",
1003
+ "you_dont_have_any_gateway": "You don't have any gateway",
1004
+ "gateway_information": "Gateway information",
1005
+ "reboot": "Reboot",
1002
1006
  "information": "Information",
1003
1007
  "gateway_list": "Gateway list",
1004
1008
  "delete_template": "Delete template",
@@ -998,6 +998,10 @@
998
998
  "no_template_yet": "Chưa có mẫu nào",
999
999
  "add_your_template": "Thêm mẫu của bạn trên web",
1000
1000
  "edit_template": "Chỉnh sửa mẫu",
1001
+ "no_gateway_yet": "Chưa có cổng nào",
1002
+ "you_dont_have_any_gateway": "Bạn không có bất kỳ cổng nào",
1003
+ "gateway_information": "Thông tin cổng",
1004
+ "reboot": "Khởi động lại",
1001
1005
  "information": "Thông tin",
1002
1006
  "gateway_list": "Danh sách cổng vào",
1003
1007
  "delete_template": "Xóa mẫu",
@@ -160,6 +160,7 @@ const Routes = {
160
160
  Template: 'Template',
161
161
  TemplateDetail: 'TemplateDetail',
162
162
  Gateway: 'Gateway',
163
+ GatewayDetail: 'GatewayDetail',
163
164
  Smart: 'Smart',
164
165
  TemplateStack: 'TemplateStack',
165
166
  GatewayStack: 'GatewayStack',