@eohjsc/react-native-smart-city 0.7.42 → 0.7.43
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 +1 -1
- package/src/commons/Device/PowerConsumptionChart.js +63 -31
- package/src/configs/AccessibilityLabel.js +1 -0
- package/src/iot/mqtt.js +4 -2
- package/src/navigations/UnitStack.js +2 -2
- package/src/screens/ActivityLog/ItemLog.js +69 -16
- package/src/screens/ActivityLog/__test__/ItemLog.test.js +169 -2
- package/src/screens/ActivityLog/index.js +2 -2
- package/src/screens/ActivityLog/styles/itemLogStyles.js +35 -0
- package/src/screens/Device/components/SensorDisplayItem.js +1 -0
- package/src/screens/Device/styles.js +1 -1
- package/src/screens/UnitSummary/components/PowerConsumption/__test__/PowerConsumption.test.js +32 -0
- package/src/utils/I18n/translations/en.js +1 -0
- package/src/utils/I18n/translations/vi.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import moment from 'moment';
|
|
2
2
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
-
import { StyleSheet, View } from 'react-native';
|
|
3
|
+
import { ActivityIndicator, StyleSheet, View } from 'react-native';
|
|
4
4
|
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
5
5
|
|
|
6
6
|
import { API, Colors } from '../../configs';
|
|
@@ -19,6 +19,7 @@ const PowerConsumptionChart = ({
|
|
|
19
19
|
powerConfigId,
|
|
20
20
|
defaultPeriod = 'date',
|
|
21
21
|
color = Colors.Primary,
|
|
22
|
+
isWidgetOrder = false,
|
|
22
23
|
style,
|
|
23
24
|
}) => {
|
|
24
25
|
const t = useTranslations();
|
|
@@ -30,9 +31,11 @@ const PowerConsumptionChart = ({
|
|
|
30
31
|
price: '',
|
|
31
32
|
});
|
|
32
33
|
const [value, setValue] = useState([moment().subtract(6, 'days'), moment()]);
|
|
34
|
+
const [isFetching, setIsFetching] = useState(false);
|
|
33
35
|
|
|
34
36
|
const fetchData = useCallback(
|
|
35
37
|
async (startDate, endDate) => {
|
|
38
|
+
setIsFetching(true);
|
|
36
39
|
let params = new URLSearchParams();
|
|
37
40
|
params.append('config', powerConfigId);
|
|
38
41
|
params.append('group_by', groupBy);
|
|
@@ -50,6 +53,7 @@ const PowerConsumptionChart = ({
|
|
|
50
53
|
if (success) {
|
|
51
54
|
setDatas(data);
|
|
52
55
|
}
|
|
56
|
+
setIsFetching(false);
|
|
53
57
|
},
|
|
54
58
|
[groupBy, powerConfigId]
|
|
55
59
|
);
|
|
@@ -83,13 +87,20 @@ const PowerConsumptionChart = ({
|
|
|
83
87
|
}, [datas, chartConfig]);
|
|
84
88
|
|
|
85
89
|
const renderChart = useMemo(() => {
|
|
90
|
+
if (isFetching) {
|
|
91
|
+
return (
|
|
92
|
+
<View style={styles.loadingWrapper}>
|
|
93
|
+
<ActivityIndicator size="large" color={Colors.Primary} />
|
|
94
|
+
</View>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
86
97
|
if (!datas || datas.length === 0) {
|
|
87
98
|
return null;
|
|
88
99
|
}
|
|
89
100
|
return (
|
|
90
101
|
<HorizontalBarChart datas={datas} config={chartConfig} color={color} />
|
|
91
102
|
);
|
|
92
|
-
}, [chartConfig, datas, color]);
|
|
103
|
+
}, [chartConfig, datas, color, isFetching]);
|
|
93
104
|
|
|
94
105
|
const selectStart = (date) => {
|
|
95
106
|
setValue((state) => [date, state[1]]);
|
|
@@ -99,16 +110,23 @@ const PowerConsumptionChart = ({
|
|
|
99
110
|
};
|
|
100
111
|
|
|
101
112
|
return (
|
|
102
|
-
<View style={style}>
|
|
113
|
+
<View style={[styles.wrapper, style]}>
|
|
103
114
|
<View style={styles.historyView}>
|
|
104
115
|
<View style={styles.titleHistory}>
|
|
105
|
-
<Text
|
|
116
|
+
<Text
|
|
117
|
+
style={styles.label}
|
|
118
|
+
bold
|
|
119
|
+
color={Colors.Gray9}
|
|
120
|
+
numberOfLines={1}
|
|
121
|
+
>
|
|
106
122
|
{label || t('power_consumption_chart')}
|
|
107
123
|
</Text>
|
|
108
124
|
|
|
109
|
-
|
|
125
|
+
{!isWidgetOrder && (
|
|
126
|
+
<ChartAggregationOption groupBy={groupBy} setGroupBy={setGroupBy} />
|
|
127
|
+
)}
|
|
110
128
|
</View>
|
|
111
|
-
{groupBy === 'date' && (
|
|
129
|
+
{!isWidgetOrder && groupBy === 'date' && (
|
|
112
130
|
<DateTimeRangeChange
|
|
113
131
|
startTime={value[0]}
|
|
114
132
|
endTime={value[1]}
|
|
@@ -119,34 +137,36 @@ const PowerConsumptionChart = ({
|
|
|
119
137
|
)}
|
|
120
138
|
</View>
|
|
121
139
|
|
|
122
|
-
|
|
123
|
-
<
|
|
124
|
-
{
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
<View style={styles.
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
140
|
+
{!isWidgetOrder && (
|
|
141
|
+
<View style={styles.calculateCostCard}>
|
|
142
|
+
<Text style={styles.title}>
|
|
143
|
+
{t('input_price_to_calculate_electricity_cost')}
|
|
144
|
+
</Text>
|
|
145
|
+
<View style={styles.inputRow}>
|
|
146
|
+
<View style={styles.inputWrapper}>
|
|
147
|
+
<CurrencyInput
|
|
148
|
+
value={price}
|
|
149
|
+
onChange={setPrice}
|
|
150
|
+
minValue={0}
|
|
151
|
+
maxValue={100000000}
|
|
152
|
+
placeholder={'0'}
|
|
153
|
+
onSubmitEditing={onCalculateCost}
|
|
154
|
+
currency="đ/kWh"
|
|
155
|
+
wrapperStyle={styles.currencyInputNoBorder}
|
|
156
|
+
/>
|
|
157
|
+
</View>
|
|
158
|
+
<Button
|
|
159
|
+
type="primary"
|
|
160
|
+
title={t('calculate_cost')}
|
|
161
|
+
height={42}
|
|
162
|
+
onPress={onCalculateCost}
|
|
163
|
+
style={styles.buttonCalculate}
|
|
164
|
+
textSemiBold={true}
|
|
165
|
+
accessibilityLabel={AccessibilityLabel.BUTTON_CALCULATE_COST}
|
|
137
166
|
/>
|
|
138
167
|
</View>
|
|
139
|
-
<Button
|
|
140
|
-
type="primary"
|
|
141
|
-
title={t('calculate_cost')}
|
|
142
|
-
height={42}
|
|
143
|
-
onPress={onCalculateCost}
|
|
144
|
-
style={styles.buttonCalculate}
|
|
145
|
-
textSemiBold={true}
|
|
146
|
-
accessibilityLabel={AccessibilityLabel.BUTTON_CALCULATE_COST}
|
|
147
|
-
/>
|
|
148
168
|
</View>
|
|
149
|
-
|
|
169
|
+
)}
|
|
150
170
|
|
|
151
171
|
<View style={styles.chartCard}>
|
|
152
172
|
{renderChart}
|
|
@@ -169,6 +189,10 @@ const PowerConsumptionChart = ({
|
|
|
169
189
|
export default React.memo(PowerConsumptionChart);
|
|
170
190
|
|
|
171
191
|
const styles = StyleSheet.create({
|
|
192
|
+
wrapper: {
|
|
193
|
+
flex: 1,
|
|
194
|
+
marginBottom: 8,
|
|
195
|
+
},
|
|
172
196
|
historyView: {
|
|
173
197
|
paddingTop: 0,
|
|
174
198
|
},
|
|
@@ -181,6 +205,7 @@ const styles = StyleSheet.create({
|
|
|
181
205
|
justifyContent: 'flex-end',
|
|
182
206
|
marginTop: 12,
|
|
183
207
|
marginLeft: 6,
|
|
208
|
+
flex: 1,
|
|
184
209
|
},
|
|
185
210
|
calculateCostCard: {
|
|
186
211
|
backgroundColor: Colors.LightPrimary,
|
|
@@ -247,5 +272,12 @@ const styles = StyleSheet.create({
|
|
|
247
272
|
label: {
|
|
248
273
|
fontSize: 18,
|
|
249
274
|
fontWeight: '700',
|
|
275
|
+
flex: 1,
|
|
276
|
+
marginRight: 8,
|
|
277
|
+
},
|
|
278
|
+
loadingWrapper: {
|
|
279
|
+
height: 150,
|
|
280
|
+
justifyContent: 'center',
|
|
281
|
+
alignItems: 'center',
|
|
250
282
|
},
|
|
251
283
|
});
|
|
@@ -138,6 +138,7 @@ export default {
|
|
|
138
138
|
BOTTOM_BUTTON_RIGHT: 'BOTTOM_BUTTON_RIGHT',
|
|
139
139
|
VIEW_BUTTON_BOTTOM_RIGHT_BUTTON: 'VIEW_BUTTON_BOTTOM_RIGHT_BUTTON',
|
|
140
140
|
VIEW_BUTTON_BOTTOM_LEFT_BUTTON: 'VIEW_BUTTON_BOTTOM_LEFT_BUTTON',
|
|
141
|
+
TOUCHABLE_OPACITY: 'TOUCHABLE_OPACITY',
|
|
141
142
|
|
|
142
143
|
// Smart parking drawer
|
|
143
144
|
ROW_EXIT_SMARTPARKING_DRAWER: 'ROW_EXIT_SMARTPARKING_DRAWER',
|
package/src/iot/mqtt.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
1
2
|
import { getObject, storeObject } from '../utils/Storage';
|
|
2
3
|
import { getLastUpdated, updateGlobalValue } from './UpdateStates';
|
|
3
|
-
import { Buffer } from 'buffer';
|
|
4
4
|
|
|
5
5
|
const struct = require('python-struct');
|
|
6
6
|
|
|
@@ -322,7 +322,9 @@ const get_offset_sample_value = async (value, config) => {
|
|
|
322
322
|
|
|
323
323
|
const getValueConverter = (value, config) => {
|
|
324
324
|
const converter = mappingValueConverter[config?.value_converter];
|
|
325
|
-
return converter
|
|
325
|
+
return converter
|
|
326
|
+
? Number(get_decimal_value(converter(value), config))
|
|
327
|
+
: value;
|
|
326
328
|
};
|
|
327
329
|
|
|
328
330
|
const check_filter = (value, config) => {
|
|
@@ -11,7 +11,7 @@ import { SCContext } from '../context';
|
|
|
11
11
|
import { Action } from '../context/actionType';
|
|
12
12
|
import { useTranslations } from '../hooks/Common/useTranslations';
|
|
13
13
|
import { unwatchAllConfigs } from '../iot/Monitor';
|
|
14
|
-
import
|
|
14
|
+
import ActivityLog from '../screens/ActivityLog';
|
|
15
15
|
import Route from '../utils/Route';
|
|
16
16
|
import { screenOptions } from './utils';
|
|
17
17
|
|
|
@@ -350,7 +350,7 @@ export const UnitStack = memo((props) => {
|
|
|
350
350
|
/>
|
|
351
351
|
<Stack.Screen
|
|
352
352
|
name={Route.ActivityLog}
|
|
353
|
-
component={
|
|
353
|
+
component={ActivityLog}
|
|
354
354
|
options={{
|
|
355
355
|
headerShown: false,
|
|
356
356
|
}}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { View } from 'react-native';
|
|
1
|
+
import React, { useState, useCallback } from 'react';
|
|
2
|
+
import { View, TouchableOpacity, Modal } from 'react-native';
|
|
3
3
|
import moment from 'moment';
|
|
4
4
|
import Text from '../../commons/Text';
|
|
5
5
|
import t from '../../hooks/Common/useTranslations';
|
|
6
6
|
import styles from './styles/itemLogStyles';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
AUTOMATE_TYPE,
|
|
9
|
+
ACTIVITY_LOG_TYPES,
|
|
10
|
+
AccessibilityLabel,
|
|
11
|
+
} from '../../configs/Constants';
|
|
8
12
|
|
|
9
13
|
const DetailEmergencyEventLog = ({ item }) => (
|
|
10
14
|
<Text style={styles.text}>
|
|
@@ -20,34 +24,83 @@ const DetailEmergencyEventLog = ({ item }) => (
|
|
|
20
24
|
);
|
|
21
25
|
|
|
22
26
|
const DetailLog = ({ item }) => {
|
|
23
|
-
|
|
27
|
+
const [showModal, setShowModal] = useState(false);
|
|
28
|
+
const { name, username, created_at, message, content_code, action_name } =
|
|
29
|
+
item;
|
|
30
|
+
|
|
31
|
+
const handlePress = useCallback(() => {
|
|
32
|
+
if (message) {
|
|
33
|
+
setShowModal(true);
|
|
34
|
+
}
|
|
35
|
+
}, [message]);
|
|
36
|
+
|
|
37
|
+
const handleClose = useCallback(() => {
|
|
38
|
+
setShowModal(false);
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
switch (content_code) {
|
|
24
42
|
case ACTIVITY_LOG_TYPES.AUTO_ACTIVATED:
|
|
25
43
|
return <Text style={styles.text}>{t('auto_activated')}</Text>;
|
|
26
44
|
case ACTIVITY_LOG_TYPES.ACTIVATED_BY:
|
|
27
45
|
return (
|
|
28
46
|
<Text style={styles.text}>
|
|
29
|
-
{
|
|
30
|
-
|
|
31
|
-
: `${t('activated_by')} `}
|
|
32
|
-
<Text style={styles.name}>{item.name || item.username}</Text>
|
|
47
|
+
{action_name ? `${action_name} ${t('by')} ` : `${t('activated_by')} `}
|
|
48
|
+
<Text style={styles.name}>{name || username}</Text>
|
|
33
49
|
</Text>
|
|
34
50
|
);
|
|
35
51
|
case ACTIVITY_LOG_TYPES.SCRIPT_UPDATED_BY:
|
|
36
52
|
return (
|
|
37
53
|
<Text style={styles.text}>
|
|
38
54
|
{`${t('script_updated_by')} `}
|
|
39
|
-
<Text style={styles.name}>{
|
|
55
|
+
<Text style={styles.name}>{name || username}</Text>
|
|
40
56
|
</Text>
|
|
41
57
|
);
|
|
42
58
|
default:
|
|
43
59
|
return (
|
|
44
|
-
|
|
45
|
-
{
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
<>
|
|
61
|
+
<TouchableOpacity onPress={handlePress} activeOpacity={0.7}>
|
|
62
|
+
<Text style={styles.text}>
|
|
63
|
+
{action_name
|
|
64
|
+
? `${action_name} ${t('by')} `
|
|
65
|
+
: `${t('activated_by')} `}
|
|
66
|
+
<Text style={styles.name}>{name || username}</Text>
|
|
67
|
+
</Text>
|
|
68
|
+
</TouchableOpacity>
|
|
69
|
+
<Modal
|
|
70
|
+
visible={showModal}
|
|
71
|
+
transparent
|
|
72
|
+
animationType="fade"
|
|
73
|
+
onRequestClose={handleClose}
|
|
74
|
+
accessibilityLabel={AccessibilityLabel.MODAL_BUTTON_POPUP}
|
|
75
|
+
>
|
|
76
|
+
<TouchableOpacity
|
|
77
|
+
style={styles.modalOverlay}
|
|
78
|
+
activeOpacity={1}
|
|
79
|
+
onPress={handleClose}
|
|
80
|
+
accessibilityLabel={AccessibilityLabel.TOUCHABLE_OPACITY}
|
|
81
|
+
>
|
|
82
|
+
<View style={styles.modalContent}>
|
|
83
|
+
<Text style={styles.modalTitle}>{t('detail')}</Text>
|
|
84
|
+
<Text style={styles.modalMessage}>
|
|
85
|
+
{action_name
|
|
86
|
+
? `${action_name} ${t('by')} `
|
|
87
|
+
: `${t('activated_by')} `}
|
|
88
|
+
<Text style={styles.name}>{name || username}</Text>
|
|
89
|
+
</Text>
|
|
90
|
+
<Text style={styles.modalMessage}>{message}</Text>
|
|
91
|
+
<Text style={styles.modalMessage}>
|
|
92
|
+
{moment(created_at).format('DD/MM/YYYY HH:mm:ss')}
|
|
93
|
+
</Text>
|
|
94
|
+
<TouchableOpacity
|
|
95
|
+
style={styles.modalCloseButton}
|
|
96
|
+
onPress={handleClose}
|
|
97
|
+
>
|
|
98
|
+
<Text style={styles.modalCloseText}>{t('close')}</Text>
|
|
99
|
+
</TouchableOpacity>
|
|
100
|
+
</View>
|
|
101
|
+
</TouchableOpacity>
|
|
102
|
+
</Modal>
|
|
103
|
+
</>
|
|
51
104
|
);
|
|
52
105
|
}
|
|
53
106
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Text } from 'react-native';
|
|
2
|
+
import { Text, TouchableOpacity, Modal } from 'react-native';
|
|
3
3
|
import { act, create } from 'react-test-renderer';
|
|
4
4
|
import ItemLog from '../ItemLog';
|
|
5
5
|
import { SCProvider } from '../../../context';
|
|
6
6
|
import { mockSCStore } from '../../../context/mockStore';
|
|
7
|
-
import { AUTOMATE_TYPE } from '../../../configs/Constants';
|
|
7
|
+
import { AUTOMATE_TYPE, AccessibilityLabel } from '../../../configs/Constants';
|
|
8
8
|
|
|
9
9
|
const wrapComponent = (props) => (
|
|
10
10
|
<SCProvider initState={mockSCStore({})}>
|
|
@@ -137,3 +137,170 @@ describe('test ItemLog emergency event', () => {
|
|
|
137
137
|
await _test(props.item.user.email);
|
|
138
138
|
});
|
|
139
139
|
});
|
|
140
|
+
|
|
141
|
+
describe('test ItemLog Modal', () => {
|
|
142
|
+
const wrapComponentWithMessage = (message) => (
|
|
143
|
+
<SCProvider initState={mockSCStore({})}>
|
|
144
|
+
<ItemLog
|
|
145
|
+
item={{
|
|
146
|
+
username: 'testuser',
|
|
147
|
+
message: message,
|
|
148
|
+
created_at: '2021-07-02T15:48:24.917932Z',
|
|
149
|
+
}}
|
|
150
|
+
type="action"
|
|
151
|
+
length={2}
|
|
152
|
+
index={0}
|
|
153
|
+
/>
|
|
154
|
+
</SCProvider>
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const getItemLogModal = (tree) => {
|
|
158
|
+
const modals = tree.root.findAllByType(Modal);
|
|
159
|
+
return modals.find(
|
|
160
|
+
(m) =>
|
|
161
|
+
m.props.accessibilityLabel === AccessibilityLabel.MODAL_BUTTON_POPUP
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const getOverlayTouchable = (tree) => {
|
|
166
|
+
const touchables = tree.root.findAllByType(TouchableOpacity);
|
|
167
|
+
return touchables.find(
|
|
168
|
+
(t) => t.props.accessibilityLabel === AccessibilityLabel.TOUCHABLE_OPACITY
|
|
169
|
+
);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
it('should open modal when clicking item with message', async () => {
|
|
173
|
+
let tree;
|
|
174
|
+
await act(async () => {
|
|
175
|
+
tree = await create(wrapComponentWithMessage('Test message content'));
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const modal = getItemLogModal(tree);
|
|
179
|
+
expect(modal.props.visible).toBe(false);
|
|
180
|
+
|
|
181
|
+
const touchables = tree.root.findAllByType(TouchableOpacity);
|
|
182
|
+
const itemTouchable = touchables[0];
|
|
183
|
+
|
|
184
|
+
await act(async () => {
|
|
185
|
+
itemTouchable.props.onPress();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(modal.props.visible).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should display correct message in modal', async () => {
|
|
192
|
+
let tree;
|
|
193
|
+
const testMessage = 'Test message content';
|
|
194
|
+
await act(async () => {
|
|
195
|
+
tree = await create(wrapComponentWithMessage(testMessage));
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const touchables = tree.root.findAllByType(TouchableOpacity);
|
|
199
|
+
await act(async () => {
|
|
200
|
+
touchables[0].props.onPress();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const texts = tree.root.findAllByType(Text);
|
|
204
|
+
const messageText = texts.find((t) => t.props.children === testMessage);
|
|
205
|
+
expect(messageText).toBeTruthy();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should close modal when pressing close button', async () => {
|
|
209
|
+
let tree;
|
|
210
|
+
await act(async () => {
|
|
211
|
+
tree = await create(wrapComponentWithMessage('Test message'));
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const modal = getItemLogModal(tree);
|
|
215
|
+
const touchables = tree.root.findAllByType(TouchableOpacity);
|
|
216
|
+
|
|
217
|
+
await act(async () => {
|
|
218
|
+
touchables[0].props.onPress();
|
|
219
|
+
});
|
|
220
|
+
expect(modal.props.visible).toBe(true);
|
|
221
|
+
|
|
222
|
+
// Re-find touchables after modal is open and find close button by style
|
|
223
|
+
const updatedTouchables = tree.root.findAllByType(TouchableOpacity);
|
|
224
|
+
const closeButton = updatedTouchables.find(
|
|
225
|
+
(t) => t.props.style?.alignItems === 'center'
|
|
226
|
+
);
|
|
227
|
+
await act(async () => {
|
|
228
|
+
closeButton.props.onPress();
|
|
229
|
+
});
|
|
230
|
+
expect(modal.props.visible).toBe(false);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should close modal when pressing overlay', async () => {
|
|
234
|
+
let tree;
|
|
235
|
+
await act(async () => {
|
|
236
|
+
tree = await create(wrapComponentWithMessage('Test message'));
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const modal = getItemLogModal(tree);
|
|
240
|
+
const touchables = tree.root.findAllByType(TouchableOpacity);
|
|
241
|
+
|
|
242
|
+
await act(async () => {
|
|
243
|
+
touchables[0].props.onPress();
|
|
244
|
+
});
|
|
245
|
+
expect(modal.props.visible).toBe(true);
|
|
246
|
+
|
|
247
|
+
// Find overlay by accessibilityLabel
|
|
248
|
+
const overlayTouchable = getOverlayTouchable(tree);
|
|
249
|
+
await act(async () => {
|
|
250
|
+
overlayTouchable.props.onPress();
|
|
251
|
+
});
|
|
252
|
+
expect(modal.props.visible).toBe(false);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should close modal on requestClose (Android back button)', async () => {
|
|
256
|
+
let tree;
|
|
257
|
+
await act(async () => {
|
|
258
|
+
tree = await create(wrapComponentWithMessage('Test message'));
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const modal = getItemLogModal(tree);
|
|
262
|
+
const touchables = tree.root.findAllByType(TouchableOpacity);
|
|
263
|
+
|
|
264
|
+
await act(async () => {
|
|
265
|
+
touchables[0].props.onPress();
|
|
266
|
+
});
|
|
267
|
+
expect(modal.props.visible).toBe(true);
|
|
268
|
+
|
|
269
|
+
await act(async () => {
|
|
270
|
+
modal.props.onRequestClose();
|
|
271
|
+
});
|
|
272
|
+
expect(modal.props.visible).toBe(false);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should not open modal when item has no message', async () => {
|
|
276
|
+
let tree;
|
|
277
|
+
await act(async () => {
|
|
278
|
+
tree = await create(wrapComponentWithMessage(null));
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const modal = getItemLogModal(tree);
|
|
282
|
+
const touchables = tree.root.findAllByType(TouchableOpacity);
|
|
283
|
+
|
|
284
|
+
await act(async () => {
|
|
285
|
+
touchables[0].props.onPress();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
expect(modal.props.visible).toBe(false);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('should not open modal when message is empty string', async () => {
|
|
292
|
+
let tree;
|
|
293
|
+
await act(async () => {
|
|
294
|
+
tree = await create(wrapComponentWithMessage(''));
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const modal = getItemLogModal(tree);
|
|
298
|
+
const touchables = tree.root.findAllByType(TouchableOpacity);
|
|
299
|
+
|
|
300
|
+
await act(async () => {
|
|
301
|
+
touchables[0].props.onPress();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
expect(modal.props.visible).toBe(false);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
@@ -20,7 +20,7 @@ import { AccessibilityLabel } from '../../configs/Constants';
|
|
|
20
20
|
|
|
21
21
|
const keyExtractor = (item) => item.id;
|
|
22
22
|
|
|
23
|
-
const
|
|
23
|
+
const ActivityLog = ({ route }) => {
|
|
24
24
|
const t = useTranslations();
|
|
25
25
|
const { id, type, share, filterEnabled } = route?.params || {};
|
|
26
26
|
const {
|
|
@@ -131,4 +131,4 @@ const ActivityLogScreen = ({ route }) => {
|
|
|
131
131
|
);
|
|
132
132
|
};
|
|
133
133
|
|
|
134
|
-
export default
|
|
134
|
+
export default ActivityLog;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Colors } from '../../../configs';
|
|
2
2
|
import { StyleSheet } from 'react-native';
|
|
3
|
+
import { colorOpacity } from '../../../utils/Converter/color';
|
|
3
4
|
|
|
4
5
|
export default StyleSheet.create({
|
|
5
6
|
wrapItem: {
|
|
@@ -41,4 +42,38 @@ export default StyleSheet.create({
|
|
|
41
42
|
lineHeight: 22,
|
|
42
43
|
flexShrink: 1,
|
|
43
44
|
},
|
|
45
|
+
modalOverlay: {
|
|
46
|
+
flex: 1,
|
|
47
|
+
backgroundColor: colorOpacity(Colors.Black, 0.5),
|
|
48
|
+
justifyContent: 'center',
|
|
49
|
+
alignItems: 'center',
|
|
50
|
+
},
|
|
51
|
+
modalContent: {
|
|
52
|
+
backgroundColor: Colors.White,
|
|
53
|
+
borderRadius: 12,
|
|
54
|
+
padding: 20,
|
|
55
|
+
marginHorizontal: 20,
|
|
56
|
+
maxWidth: '90%',
|
|
57
|
+
minWidth: '80%',
|
|
58
|
+
},
|
|
59
|
+
modalTitle: {
|
|
60
|
+
fontSize: 18,
|
|
61
|
+
fontWeight: 'bold',
|
|
62
|
+
color: Colors.Gray9,
|
|
63
|
+
marginBottom: 12,
|
|
64
|
+
},
|
|
65
|
+
modalMessage: {
|
|
66
|
+
marginBottom: 15,
|
|
67
|
+
},
|
|
68
|
+
modalCloseButton: {
|
|
69
|
+
backgroundColor: Colors.Primary,
|
|
70
|
+
borderRadius: 8,
|
|
71
|
+
paddingVertical: 12,
|
|
72
|
+
alignItems: 'center',
|
|
73
|
+
},
|
|
74
|
+
modalCloseText: {
|
|
75
|
+
color: Colors.White,
|
|
76
|
+
fontSize: 16,
|
|
77
|
+
fontWeight: '600',
|
|
78
|
+
},
|
|
44
79
|
});
|
package/src/screens/UnitSummary/components/PowerConsumption/__test__/PowerConsumption.test.js
CHANGED
|
@@ -2,6 +2,7 @@ import MockAdapter from 'axios-mock-adapter';
|
|
|
2
2
|
import renderer, { act, create } from 'react-test-renderer';
|
|
3
3
|
|
|
4
4
|
import moment from 'moment';
|
|
5
|
+
import { ActivityIndicator } from 'react-native';
|
|
5
6
|
import { Today } from '../../../../../commons';
|
|
6
7
|
import DateTimeRangeChange from '../../../../../commons/DateTimeRangeChange';
|
|
7
8
|
import PowerConsumptionChart from '../../../../../commons/Device/PowerConsumptionChart';
|
|
@@ -308,4 +309,35 @@ describe('Test PowerConsumption', () => {
|
|
|
308
309
|
powerConsumptionChart.findByType(DateTimeRangeChange).props.endTime
|
|
309
310
|
).toEqual(newEndDate);
|
|
310
311
|
});
|
|
312
|
+
|
|
313
|
+
it('should show ActivityIndicator when isFetching is true', async () => {
|
|
314
|
+
const summaryDetail = {
|
|
315
|
+
listConfigs: {
|
|
316
|
+
total_power: 207,
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
let resolvePromise;
|
|
321
|
+
const pendingPromise = new Promise((resolve) => {
|
|
322
|
+
resolvePromise = resolve;
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
mock.onGet(API.VALUE_CONSUME.DISPLAY_HISTORY()).reply(async () => {
|
|
326
|
+
await pendingPromise;
|
|
327
|
+
return [200, []];
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
await act(async () => {
|
|
331
|
+
tree = await create(wrapComponent(summaryDetail));
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const instance = tree.root;
|
|
335
|
+
const activityIndicator = instance.findByType(ActivityIndicator);
|
|
336
|
+
expect(activityIndicator).toBeDefined();
|
|
337
|
+
expect(activityIndicator.props.size).toEqual('large');
|
|
338
|
+
|
|
339
|
+
await act(async () => {
|
|
340
|
+
resolvePromise();
|
|
341
|
+
});
|
|
342
|
+
});
|
|
311
343
|
});
|
|
@@ -1348,6 +1348,7 @@ export default {
|
|
|
1348
1348
|
not_activated: 'Not activated',
|
|
1349
1349
|
open: 'Open',
|
|
1350
1350
|
close: 'Close',
|
|
1351
|
+
detail: 'Detail',
|
|
1351
1352
|
create_contact_success: 'Create contact success!',
|
|
1352
1353
|
can_not_login_to_current_ssid: "Can't login to current SSID",
|
|
1353
1354
|
confirm_password_not_match: 'Confirm password does not match',
|
|
@@ -1346,6 +1346,7 @@ export default {
|
|
|
1346
1346
|
not_activated: 'Không kích hoạt',
|
|
1347
1347
|
open: 'Mở',
|
|
1348
1348
|
close: 'Đóng',
|
|
1349
|
+
detail: 'Chi tiết',
|
|
1349
1350
|
create_contact_success: 'Tạo liên hệ thành công!',
|
|
1350
1351
|
can_not_login_to_current_ssid: 'Không thể đăng nhập vào SSID hiện tại',
|
|
1351
1352
|
confirm_password_not_match: 'Mật khẩu xác nhận không trùng khớp',
|