@eohjsc/react-native-smart-city 0.2.48 → 0.2.52
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 +9 -0
- package/package.json +2 -3
- package/src/commons/Device/HistoryChart.js +133 -14
- package/src/commons/Device/HorizontalBarChart.js +30 -3
- package/src/commons/Device/LinearChart.js +2 -7
- package/src/commons/ThreeButtonHistory/CalendarHeader.js +35 -0
- package/src/commons/ThreeButtonHistory/CalendarHeaderStyles.js +17 -0
- package/src/commons/ThreeButtonHistory/SelectMonth.js +53 -0
- package/src/commons/ThreeButtonHistory/SelectMonthStyles.js +29 -0
- package/src/commons/ThreeButtonHistory/__test__/SelectMonth.test.js +37 -0
- package/src/commons/ThreeButtonHistory/__test__/ThreeButtonHistory.test.js +231 -0
- package/src/commons/ThreeButtonHistory/index.js +281 -0
- package/src/commons/ThreeButtonHistory/styles.js +65 -0
- package/src/configs/Constants.js +4 -0
- package/src/screens/AddNewOneTap/__test__/AddNewOneTap.test.js +10 -1
- package/src/screens/AddNewOneTap/index.js +3 -3
- package/src/screens/SetSchedule/__test__/SelectWeekday.test.js +2 -2
- package/src/screens/UnitSummary/components/3PPowerConsumption/index.js +17 -5
- package/src/screens/UnitSummary/components/PowerConsumption/index.js +19 -6
- package/src/screens/UnitSummary/index.js +1 -5
- package/src/utils/I18n/translations/en.json +3 -0
- package/src/utils/I18n/translations/vi.json +3 -0
package/README.md
CHANGED
|
@@ -24,6 +24,15 @@ import SmartCity, {
|
|
|
24
24
|
AddUnitStack,
|
|
25
25
|
SharedStack,
|
|
26
26
|
Explore,
|
|
27
|
+
SCProvider,
|
|
28
|
+
SCContext,
|
|
29
|
+
AddLGDeviceStack,
|
|
30
|
+
initSCConfig,
|
|
31
|
+
AutomateStack,
|
|
32
|
+
NotificationStack,
|
|
33
|
+
MyPinnedSharedUnit,
|
|
34
|
+
SharedUnit,
|
|
35
|
+
MyUnit,
|
|
27
36
|
} from '@eohjsc/react-native-smart-city';
|
|
28
37
|
import { createStackNavigator } from '@react-navigation/stack';
|
|
29
38
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eohjsc/react-native-smart-city",
|
|
3
3
|
"title": "React Native Smart Home",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.52",
|
|
5
5
|
"description": "TODO",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
@@ -39,7 +39,6 @@
|
|
|
39
39
|
"reset-cache": "react-native start --reset-cache",
|
|
40
40
|
"merge_conflict": "yarn jest && yarn update_coverage_result && yarn check_coverage_config",
|
|
41
41
|
"jest": "jest --detectOpenHandles",
|
|
42
|
-
"postinstall": "patch-package && npx husky install",
|
|
43
42
|
"example": "yarn --cwd example",
|
|
44
43
|
"pods": "cd example && pod-install --quiet",
|
|
45
44
|
"bootstrap": "yarn example && yarn && yarn pods",
|
|
@@ -86,7 +85,7 @@
|
|
|
86
85
|
"eslint-plugin-react": "^7.21.5",
|
|
87
86
|
"eslint-plugin-react-native": "^3.10.0",
|
|
88
87
|
"factory-girl": "^5.0.4",
|
|
89
|
-
"husky": "^2.
|
|
88
|
+
"husky": "^2.7.0",
|
|
90
89
|
"jest": "^26.6.3",
|
|
91
90
|
"jest-circus": "^26.6.3",
|
|
92
91
|
"jetifier": "^1.6.6",
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import React, { memo, useCallback, useState } from 'react';
|
|
1
|
+
import React, { memo, useCallback, useState, useMemo } from 'react';
|
|
2
2
|
import { StyleSheet, View, Dimensions } from 'react-native';
|
|
3
3
|
import moment from 'moment';
|
|
4
4
|
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
5
5
|
|
|
6
6
|
import { Colors } from '../../configs';
|
|
7
7
|
import Text from '../../commons/Text';
|
|
8
|
+
import TextInput from '../../commons/Form/TextInput';
|
|
9
|
+
import Button from '../../commons/Button';
|
|
8
10
|
import DateTimeRangeChange from '../DateTimeRangeChange';
|
|
9
11
|
import HorizontalBarChart from './HorizontalBarChart';
|
|
10
12
|
import DateTimePickerModal from 'react-native-modal-datetime-picker';
|
|
11
13
|
import LinearChart from './LinearChart';
|
|
14
|
+
import ThreeButtonHistory from '../ThreeButtonHistory';
|
|
15
|
+
import { formatMoney } from '../../utils/Utils';
|
|
12
16
|
|
|
13
17
|
export const dateTimeType = {
|
|
14
18
|
date: 'date',
|
|
@@ -21,12 +25,15 @@ const { width } = Dimensions.get('window');
|
|
|
21
25
|
const HistoryChart = memo(
|
|
22
26
|
({
|
|
23
27
|
datas,
|
|
24
|
-
|
|
28
|
+
chartConfig,
|
|
29
|
+
setChartConfig,
|
|
25
30
|
style,
|
|
26
31
|
formatType,
|
|
27
32
|
startDate,
|
|
33
|
+
endDate,
|
|
28
34
|
setEndDate,
|
|
29
35
|
setStartDate,
|
|
36
|
+
setGroupBy,
|
|
30
37
|
configuration,
|
|
31
38
|
}) => {
|
|
32
39
|
const t = useTranslations();
|
|
@@ -42,6 +49,9 @@ const HistoryChart = memo(
|
|
|
42
49
|
startTime: startDate ? startDate : moment().subtract(1, 'day').valueOf(),
|
|
43
50
|
endTime: dateNow,
|
|
44
51
|
});
|
|
52
|
+
const [price, setPrice] = useState('');
|
|
53
|
+
const [priceDisplay, setPriceDisplay] = useState('');
|
|
54
|
+
|
|
45
55
|
const onStart = useCallback(() => {
|
|
46
56
|
setEventPicker({
|
|
47
57
|
...eventPicker,
|
|
@@ -126,6 +136,40 @@ const HistoryChart = memo(
|
|
|
126
136
|
},
|
|
127
137
|
[chartOptions]
|
|
128
138
|
);
|
|
139
|
+
|
|
140
|
+
const onChangePrice = useCallback(
|
|
141
|
+
(input) => {
|
|
142
|
+
const validInput = input.replace(/\D/g, '');
|
|
143
|
+
setPrice(validInput);
|
|
144
|
+
setPriceDisplay(validInput);
|
|
145
|
+
},
|
|
146
|
+
[setPrice, setPriceDisplay]
|
|
147
|
+
);
|
|
148
|
+
const onPriceFocus = useCallback(() => {
|
|
149
|
+
setPriceDisplay(price);
|
|
150
|
+
}, [price]);
|
|
151
|
+
const onPriceBlur = useCallback(() => {
|
|
152
|
+
setPriceDisplay(price !== '' ? formatMoney(price) : '');
|
|
153
|
+
}, [price]);
|
|
154
|
+
const onCalculateCost = useCallback(() => {
|
|
155
|
+
setChartConfig((config) => ({
|
|
156
|
+
...config,
|
|
157
|
+
price,
|
|
158
|
+
}));
|
|
159
|
+
}, [setChartConfig, price]);
|
|
160
|
+
|
|
161
|
+
const totalPrice = useMemo(() => {
|
|
162
|
+
if (configuration.config !== 'power_consumption') {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const { price } = chartConfig;
|
|
166
|
+
if (price === '' || isNaN(price)) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const sum = datas[0].data.reduce((a, b) => a + b.y, 0);
|
|
170
|
+
return sum * price;
|
|
171
|
+
}, [configuration, datas, chartConfig]);
|
|
172
|
+
|
|
129
173
|
const renderChart = useCallback(() => {
|
|
130
174
|
if (configuration.type === 'line_chart') {
|
|
131
175
|
return (
|
|
@@ -137,7 +181,7 @@ const HistoryChart = memo(
|
|
|
137
181
|
);
|
|
138
182
|
}
|
|
139
183
|
if (configuration.type === 'horizontal_bar_chart') {
|
|
140
|
-
return <HorizontalBarChart
|
|
184
|
+
return <HorizontalBarChart datas={datas} config={chartConfig} />;
|
|
141
185
|
}
|
|
142
186
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
143
187
|
}, [configuration, datas, chartOptions, onShowOneChart]);
|
|
@@ -145,19 +189,66 @@ const HistoryChart = memo(
|
|
|
145
189
|
return (
|
|
146
190
|
<View style={style}>
|
|
147
191
|
<View style={styles.historyView}>
|
|
148
|
-
<
|
|
149
|
-
{
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
192
|
+
<View style={styles.titleHistory}>
|
|
193
|
+
<Text size={20} semibold color={Colors.Gray9}>
|
|
194
|
+
{t('history')}
|
|
195
|
+
</Text>
|
|
196
|
+
{configuration.type === 'horizontal_bar_chart' && (
|
|
197
|
+
<ThreeButtonHistory
|
|
198
|
+
setStartDate={setStartDate}
|
|
199
|
+
setEndDate={setEndDate}
|
|
200
|
+
setGroupBy={setGroupBy}
|
|
201
|
+
startDate={startDate}
|
|
202
|
+
endDate={endDate}
|
|
203
|
+
/>
|
|
204
|
+
)}
|
|
205
|
+
</View>
|
|
206
|
+
{configuration.type !== 'horizontal_bar_chart' && (
|
|
207
|
+
<DateTimeRangeChange
|
|
208
|
+
startTime={eventPicker.startTime}
|
|
209
|
+
onStart={onStart}
|
|
210
|
+
onEnd={onEnd}
|
|
211
|
+
endTime={eventPicker.endTime}
|
|
212
|
+
date={dateNow}
|
|
213
|
+
formatType={formatType}
|
|
214
|
+
inline={false}
|
|
215
|
+
/>
|
|
216
|
+
)}
|
|
159
217
|
</View>
|
|
218
|
+
{configuration.config === 'power_consumption' && (
|
|
219
|
+
<View style={styles.wrapCalculateCost}>
|
|
220
|
+
<Text type="H4">
|
|
221
|
+
{t('input_price_to_calculate_electricity_cost')}
|
|
222
|
+
</Text>
|
|
223
|
+
<View style={styles.row}>
|
|
224
|
+
<TextInput
|
|
225
|
+
onFocus={onPriceFocus}
|
|
226
|
+
onBlur={onPriceBlur}
|
|
227
|
+
value={priceDisplay}
|
|
228
|
+
onChange={onChangePrice}
|
|
229
|
+
onSubmitEditing={onCalculateCost}
|
|
230
|
+
placeholder={'0đ'}
|
|
231
|
+
keyboardType="numeric"
|
|
232
|
+
textInputStyle={styles.textInput}
|
|
233
|
+
wrapStyle={styles.wrapTextInput}
|
|
234
|
+
borderBottomOnly
|
|
235
|
+
/>
|
|
236
|
+
<Button
|
|
237
|
+
type="primary"
|
|
238
|
+
title={t('calculate_cost')}
|
|
239
|
+
onPress={onCalculateCost}
|
|
240
|
+
style={styles.buttonCalculate}
|
|
241
|
+
textSemiBold={false}
|
|
242
|
+
/>
|
|
243
|
+
</View>
|
|
244
|
+
</View>
|
|
245
|
+
)}
|
|
160
246
|
{renderChart()}
|
|
247
|
+
{configuration.config === 'power_consumption' && !!chartConfig.price && (
|
|
248
|
+
<Text type="H4">
|
|
249
|
+
{t('total_power_price')} <Text bold>{formatMoney(totalPrice)}</Text>
|
|
250
|
+
</Text>
|
|
251
|
+
)}
|
|
161
252
|
<DateTimePickerModal
|
|
162
253
|
isVisible={eventPicker.showModalStart}
|
|
163
254
|
date={eventPicker.startTime}
|
|
@@ -185,6 +276,11 @@ const styles = StyleSheet.create({
|
|
|
185
276
|
historyView: {
|
|
186
277
|
paddingTop: 16,
|
|
187
278
|
},
|
|
279
|
+
titleHistory: {
|
|
280
|
+
flexDirection: 'row',
|
|
281
|
+
alignItems: 'center',
|
|
282
|
+
justifyContent: 'space-between',
|
|
283
|
+
},
|
|
188
284
|
chartInfo: {
|
|
189
285
|
flexDirection: 'row',
|
|
190
286
|
justifyContent: 'center',
|
|
@@ -196,4 +292,27 @@ const styles = StyleSheet.create({
|
|
|
196
292
|
chart: {
|
|
197
293
|
height: 300,
|
|
198
294
|
},
|
|
295
|
+
wrapCalculateCost: {
|
|
296
|
+
marginTop: 12,
|
|
297
|
+
},
|
|
298
|
+
row: {
|
|
299
|
+
flexDirection: 'row',
|
|
300
|
+
alignItems: 'center',
|
|
301
|
+
},
|
|
302
|
+
wrapTextInput: {
|
|
303
|
+
flex: 1,
|
|
304
|
+
marginRight: 16,
|
|
305
|
+
},
|
|
306
|
+
textInput: {
|
|
307
|
+
marginTop: 0,
|
|
308
|
+
paddingTop: 0,
|
|
309
|
+
paddingLeft: 0,
|
|
310
|
+
fontSize: 18,
|
|
311
|
+
lineHeight: 24,
|
|
312
|
+
},
|
|
313
|
+
buttonCalculate: {
|
|
314
|
+
width: null,
|
|
315
|
+
flex: null,
|
|
316
|
+
paddingHorizontal: 12,
|
|
317
|
+
},
|
|
199
318
|
});
|
|
@@ -5,7 +5,7 @@ import HighchartsReactNative from '@highcharts/highcharts-react-native/src/Highc
|
|
|
5
5
|
import { Colors } from '../../configs';
|
|
6
6
|
import { getMaxValueIndex } from '../../utils/chartHelper/getMaxValueIndex';
|
|
7
7
|
|
|
8
|
-
const HorizontalBarChart = memo(({ datas,
|
|
8
|
+
const HorizontalBarChart = memo(({ datas, config }) => {
|
|
9
9
|
const dataY = datas[0].data.map((item, index) => {
|
|
10
10
|
return {
|
|
11
11
|
color: index % 2 === 0 ? Colors.Primary + '20' : Colors.Primary + '16',
|
|
@@ -18,6 +18,7 @@ const HorizontalBarChart = memo(({ datas, unit }) => {
|
|
|
18
18
|
}, [dataX]);
|
|
19
19
|
const maxY = getMaxValueIndex(dataY);
|
|
20
20
|
dataY.splice(maxY._index, 1, { ...maxY.max, color: Colors.Primary });
|
|
21
|
+
|
|
21
22
|
// eslint-disable-next-line no-unused-vars
|
|
22
23
|
const chartOptions = {
|
|
23
24
|
chart: {
|
|
@@ -48,7 +49,7 @@ const HorizontalBarChart = memo(({ datas, unit }) => {
|
|
|
48
49
|
{
|
|
49
50
|
marker: { enabled: true },
|
|
50
51
|
color: Colors.Primary + '50',
|
|
51
|
-
name:
|
|
52
|
+
name: JSON.stringify(config),
|
|
52
53
|
data: dataY,
|
|
53
54
|
},
|
|
54
55
|
],
|
|
@@ -76,7 +77,33 @@ const HorizontalBarChart = memo(({ datas, unit }) => {
|
|
|
76
77
|
bar: {
|
|
77
78
|
dataLabels: {
|
|
78
79
|
enabled: true,
|
|
79
|
-
|
|
80
|
+
useHTML: true,
|
|
81
|
+
formatter: function () {
|
|
82
|
+
const { unit, price } = JSON.parse(this.series.name);
|
|
83
|
+
const textColor = this.color === '#00979D' ? '#FFFFFF' : '#262626';
|
|
84
|
+
const valueStyle = `color:${textColor};font-weight:normal;font-size:12px;`;
|
|
85
|
+
const costStyle = valueStyle + 'font-weight:bold;';
|
|
86
|
+
|
|
87
|
+
let label = `<span style="${valueStyle}">` + `${this.y}${unit}`;
|
|
88
|
+
if (price === '' || isNaN(price)) {
|
|
89
|
+
return label + '</span>';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const formatMoney = (number) => {
|
|
93
|
+
return (
|
|
94
|
+
parseInt(number, 10)
|
|
95
|
+
.toFixed(0)
|
|
96
|
+
.toString()
|
|
97
|
+
.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1.') + 'đ'
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
return (
|
|
101
|
+
label +
|
|
102
|
+
`/<span style="${costStyle}">` +
|
|
103
|
+
formatMoney(this.y * price) +
|
|
104
|
+
'</span></span>'
|
|
105
|
+
);
|
|
106
|
+
},
|
|
80
107
|
align: 'right',
|
|
81
108
|
},
|
|
82
109
|
labels: {
|
|
@@ -51,15 +51,10 @@ const chartOptions = {
|
|
|
51
51
|
const time = new Date(this.value || value);
|
|
52
52
|
let date = ('0' + time.getDate()).slice(-2);
|
|
53
53
|
let month = ('0' + (time.getMonth() + 1)).slice(-2);
|
|
54
|
-
|
|
55
|
-
let minutes = ('0' + time.getMinutes()).slice(-2);
|
|
56
|
-
if (hours === '00' && minutes === '00') {
|
|
57
|
-
return `${date}.${month}`;
|
|
58
|
-
}
|
|
59
|
-
return `${hours}:${minutes}`;
|
|
54
|
+
return `${date}.${month}`;
|
|
60
55
|
},
|
|
61
56
|
},
|
|
62
|
-
minRange: 3600 * 1000,
|
|
57
|
+
minRange: 3600 * 24 * 1000,
|
|
63
58
|
},
|
|
64
59
|
plotOptions: {
|
|
65
60
|
series: {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
TouchableOpacity,
|
|
5
|
+
TouchableWithoutFeedback,
|
|
6
|
+
Image,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
import Text from '../Text';
|
|
9
|
+
import { Images } from '../../configs';
|
|
10
|
+
import styles from './CalendarHeaderStyles';
|
|
11
|
+
|
|
12
|
+
const CalendarHeader = ({
|
|
13
|
+
currentMonth,
|
|
14
|
+
onAddMonth,
|
|
15
|
+
onSubtractMonth,
|
|
16
|
+
onPressTitle,
|
|
17
|
+
}) => {
|
|
18
|
+
return (
|
|
19
|
+
<View style={styles.wrap}>
|
|
20
|
+
<TouchableOpacity onPress={onSubtractMonth} style={styles.button}>
|
|
21
|
+
<Image source={Images.arrowLeft} />
|
|
22
|
+
</TouchableOpacity>
|
|
23
|
+
<TouchableWithoutFeedback onPress={onPressTitle}>
|
|
24
|
+
<Text type="H4" bold>
|
|
25
|
+
{currentMonth.format('MMMM YYYY')}
|
|
26
|
+
</Text>
|
|
27
|
+
</TouchableWithoutFeedback>
|
|
28
|
+
<TouchableOpacity onPress={onAddMonth} style={styles.button}>
|
|
29
|
+
<Image source={Images.arrowLeft} style={styles.arrow} />
|
|
30
|
+
</TouchableOpacity>
|
|
31
|
+
</View>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default CalendarHeader;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export default StyleSheet.create({
|
|
4
|
+
wrap: {
|
|
5
|
+
flexDirection: 'row',
|
|
6
|
+
alignItems: 'center',
|
|
7
|
+
justifyContent: 'space-between',
|
|
8
|
+
paddingVertical: 16,
|
|
9
|
+
paddingHorizontal: 24,
|
|
10
|
+
},
|
|
11
|
+
arrow: {
|
|
12
|
+
transform: [{ rotate: '180deg' }],
|
|
13
|
+
},
|
|
14
|
+
button: {
|
|
15
|
+
padding: 8,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React, { memo, useMemo, useCallback } from 'react';
|
|
2
|
+
import { View, TouchableOpacity } from 'react-native';
|
|
3
|
+
import moment from 'moment';
|
|
4
|
+
|
|
5
|
+
import Text from '../Text';
|
|
6
|
+
import styles from './SelectMonthStyles';
|
|
7
|
+
import { Colors } from '../../configs';
|
|
8
|
+
|
|
9
|
+
const SelectMonth = memo(({ selectedMonth, setSelectedMonth }) => {
|
|
10
|
+
const months = useMemo(() => {
|
|
11
|
+
return Array.from({ length: 12 }, (_, i) => moment(selectedMonth).month(i));
|
|
12
|
+
}, [selectedMonth]);
|
|
13
|
+
|
|
14
|
+
const MonthItem = useCallback(
|
|
15
|
+
({ month }) => {
|
|
16
|
+
const isSelected = month.isSame(selectedMonth, 'month');
|
|
17
|
+
const isDisabled = month.isAfter(moment(), 'month');
|
|
18
|
+
const textColor = isSelected
|
|
19
|
+
? Colors.White
|
|
20
|
+
: isDisabled
|
|
21
|
+
? Colors.Gray6
|
|
22
|
+
: Colors.Gray9;
|
|
23
|
+
const backgroundColor = isSelected ? Colors.Primary : Colors.Transparent;
|
|
24
|
+
|
|
25
|
+
const onPress = () => setSelectedMonth(month);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<View style={styles.wrapItem}>
|
|
29
|
+
<TouchableOpacity
|
|
30
|
+
onPress={onPress}
|
|
31
|
+
disabled={isDisabled}
|
|
32
|
+
style={[styles.item, { backgroundColor }]}
|
|
33
|
+
>
|
|
34
|
+
<Text color={textColor} semibold>
|
|
35
|
+
{month.format('MMM')}
|
|
36
|
+
</Text>
|
|
37
|
+
</TouchableOpacity>
|
|
38
|
+
</View>
|
|
39
|
+
);
|
|
40
|
+
},
|
|
41
|
+
[selectedMonth, setSelectedMonth]
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<View style={styles.wrap}>
|
|
46
|
+
{months.map((month, index) => (
|
|
47
|
+
<MonthItem key={index} month={month} />
|
|
48
|
+
))}
|
|
49
|
+
</View>
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export default SelectMonth;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import { Constants } from '../../configs';
|
|
3
|
+
|
|
4
|
+
const marginItem = 40;
|
|
5
|
+
const marginHorizontal = 24;
|
|
6
|
+
const widthItem = (Constants.width - marginHorizontal * 2 - marginItem * 2) / 3;
|
|
7
|
+
const heightItem = (widthItem / 36) * 16;
|
|
8
|
+
|
|
9
|
+
export default StyleSheet.create({
|
|
10
|
+
wrap: {
|
|
11
|
+
marginHorizontal: 24,
|
|
12
|
+
flexDirection: 'row',
|
|
13
|
+
justifyContent: 'space-between',
|
|
14
|
+
flexWrap: 'wrap',
|
|
15
|
+
},
|
|
16
|
+
wrapItem: {
|
|
17
|
+
width: widthItem,
|
|
18
|
+
height: heightItem,
|
|
19
|
+
marginBottom: 4,
|
|
20
|
+
paddingVertical: 4,
|
|
21
|
+
paddingHorizontal: 8,
|
|
22
|
+
},
|
|
23
|
+
item: {
|
|
24
|
+
flex: 1,
|
|
25
|
+
justifyContent: 'center',
|
|
26
|
+
alignItems: 'center',
|
|
27
|
+
borderRadius: 4,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { act, create } from 'react-test-renderer';
|
|
3
|
+
import { TouchableOpacity } from 'react-native';
|
|
4
|
+
import SelectMonth from '../SelectMonth';
|
|
5
|
+
import moment from 'moment';
|
|
6
|
+
|
|
7
|
+
jest.mock('react', () => {
|
|
8
|
+
return {
|
|
9
|
+
...jest.requireActual('react'),
|
|
10
|
+
memo: (x) => x,
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('test SelectMonth', () => {
|
|
15
|
+
let tree;
|
|
16
|
+
const mockSetSelectedMonth = jest.fn();
|
|
17
|
+
const currentMonth = moment();
|
|
18
|
+
|
|
19
|
+
act(() => {
|
|
20
|
+
tree = create(
|
|
21
|
+
<SelectMonth
|
|
22
|
+
selectedMonth={currentMonth}
|
|
23
|
+
setSelectedMonth={mockSetSelectedMonth}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
const instance = tree.root;
|
|
28
|
+
const monthItems = instance.findAllByType(TouchableOpacity);
|
|
29
|
+
expect(monthItems).toHaveLength(12);
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < 12; i++) {
|
|
32
|
+
act(() => {
|
|
33
|
+
monthItems[i].props.onPress();
|
|
34
|
+
});
|
|
35
|
+
expect(mockSetSelectedMonth).toBeCalledWith(moment(currentMonth).month(i));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { act, create } from 'react-test-renderer';
|
|
3
|
+
import { SCProvider } from '../../../context';
|
|
4
|
+
import { mockSCStore } from '../../../context/mockStore';
|
|
5
|
+
import ThreeButtonHistory from '../index';
|
|
6
|
+
import moment from 'moment';
|
|
7
|
+
import SelectMonth from '../SelectMonth';
|
|
8
|
+
import { TESTID } from '../../../configs/Constants';
|
|
9
|
+
import { TouchableOpacity } from 'react-native';
|
|
10
|
+
import CalendarHeader from '../CalendarHeader';
|
|
11
|
+
import ViewButtonBottom from '../../ViewButtonBottom';
|
|
12
|
+
import BottomSheet from '../../BottomSheet';
|
|
13
|
+
import styles from '../styles';
|
|
14
|
+
|
|
15
|
+
const wrapComponent = (props) => (
|
|
16
|
+
<SCProvider initState={mockSCStore({})}>
|
|
17
|
+
<ThreeButtonHistory {...props} />
|
|
18
|
+
</SCProvider>
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
jest.mock('react', () => {
|
|
22
|
+
return {
|
|
23
|
+
...jest.requireActual('react'),
|
|
24
|
+
memo: (x) => x,
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('test ThreeButtonHistory', () => {
|
|
29
|
+
Date.now = jest.fn(() => new Date('2021-09-09T10:00:00.000Z'));
|
|
30
|
+
let tree;
|
|
31
|
+
let props;
|
|
32
|
+
const startDate = moment().add(-7, 'days').valueOf();
|
|
33
|
+
const endDate = moment().valueOf();
|
|
34
|
+
const mockSetStartDate = jest.fn();
|
|
35
|
+
const mockSetEndDate = jest.fn();
|
|
36
|
+
const mockSetGroupBy = jest.fn();
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
mockSetStartDate.mockClear();
|
|
40
|
+
mockSetEndDate.mockClear();
|
|
41
|
+
mockSetGroupBy.mockClear();
|
|
42
|
+
props = {
|
|
43
|
+
startDate,
|
|
44
|
+
endDate,
|
|
45
|
+
setStartDate: mockSetStartDate,
|
|
46
|
+
setEndDate: mockSetEndDate,
|
|
47
|
+
setGroupBy: mockSetGroupBy,
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const openCalendar = async (instance) => {
|
|
52
|
+
const historyButtons = instance.findAll(
|
|
53
|
+
(el) =>
|
|
54
|
+
el.props.testID === TESTID.HISTORY_BUTTON &&
|
|
55
|
+
el.type === TouchableOpacity
|
|
56
|
+
);
|
|
57
|
+
expect(historyButtons).toHaveLength(3);
|
|
58
|
+
await act(async () => {
|
|
59
|
+
await historyButtons[2].props.onPress();
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const testSelectGroupBy = async (index, groupBy) => {
|
|
64
|
+
await act(async () => {
|
|
65
|
+
tree = await create(wrapComponent(props));
|
|
66
|
+
});
|
|
67
|
+
const instance = tree.root;
|
|
68
|
+
const historyButtons = instance.findAll(
|
|
69
|
+
(el) =>
|
|
70
|
+
el.props.testID === TESTID.HISTORY_BUTTON &&
|
|
71
|
+
el.type === TouchableOpacity
|
|
72
|
+
);
|
|
73
|
+
await act(async () => {
|
|
74
|
+
await historyButtons[index].props.onPress();
|
|
75
|
+
});
|
|
76
|
+
expect(mockSetGroupBy).toBeCalledWith(groupBy);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
test('test group by date', async () => {
|
|
80
|
+
await testSelectGroupBy(2, 'date');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('test group by week', async () => {
|
|
84
|
+
await testSelectGroupBy(0, 'week');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('test group by month', async () => {
|
|
88
|
+
await testSelectGroupBy(1, 'month');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('test calendar change month', async () => {
|
|
92
|
+
await act(async () => {
|
|
93
|
+
tree = await create(wrapComponent(props));
|
|
94
|
+
});
|
|
95
|
+
const instance = tree.root;
|
|
96
|
+
|
|
97
|
+
await openCalendar(instance);
|
|
98
|
+
const calendar = instance.find(
|
|
99
|
+
(el) => el.props.testID === TESTID.HISTORY_CALENDAR
|
|
100
|
+
);
|
|
101
|
+
const calendarHeader = instance.findByType(CalendarHeader);
|
|
102
|
+
expect(calendarHeader.props.currentMonth.toString()).toBe(
|
|
103
|
+
moment().toString()
|
|
104
|
+
);
|
|
105
|
+
// Click calendar title
|
|
106
|
+
await act(async () => {
|
|
107
|
+
await calendarHeader.props.onPressTitle();
|
|
108
|
+
});
|
|
109
|
+
const selectMonth = instance.findByType(SelectMonth);
|
|
110
|
+
expect(selectMonth).toBeDefined();
|
|
111
|
+
expect(calendar.props.style).toBe(styles.displayNone);
|
|
112
|
+
// Click arrow left
|
|
113
|
+
await act(async () => {
|
|
114
|
+
await calendarHeader.props.onSubtractMonth();
|
|
115
|
+
});
|
|
116
|
+
expect(calendarHeader.props.currentMonth.toString()).toBe(
|
|
117
|
+
moment().add(-1, 'month').toString()
|
|
118
|
+
);
|
|
119
|
+
// Click arrow right
|
|
120
|
+
await act(async () => {
|
|
121
|
+
await calendarHeader.props.onAddMonth();
|
|
122
|
+
});
|
|
123
|
+
expect(calendarHeader.props.currentMonth.toString()).toBe(
|
|
124
|
+
moment().toString()
|
|
125
|
+
);
|
|
126
|
+
// Click arrow right
|
|
127
|
+
await act(async () => {
|
|
128
|
+
await calendarHeader.props.onAddMonth();
|
|
129
|
+
});
|
|
130
|
+
expect(calendarHeader.props.currentMonth.toString()).toBe(
|
|
131
|
+
moment().toString()
|
|
132
|
+
); // exceeded limit
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('test press Done', async () => {
|
|
136
|
+
await act(async () => {
|
|
137
|
+
tree = await create(wrapComponent(props));
|
|
138
|
+
});
|
|
139
|
+
const instance = tree.root;
|
|
140
|
+
const modal = instance.findByType(BottomSheet);
|
|
141
|
+
|
|
142
|
+
await openCalendar(instance);
|
|
143
|
+
expect(modal.props.isVisible).toBeTruthy();
|
|
144
|
+
|
|
145
|
+
const calendar = instance.find(
|
|
146
|
+
(el) => el.props.testID === TESTID.HISTORY_CALENDAR
|
|
147
|
+
);
|
|
148
|
+
const calendarHeader = instance.findByType(CalendarHeader);
|
|
149
|
+
const viewButtonBottom = instance.findByType(ViewButtonBottom);
|
|
150
|
+
// Click calendar title
|
|
151
|
+
await act(async () => {
|
|
152
|
+
await calendarHeader.props.onPressTitle();
|
|
153
|
+
});
|
|
154
|
+
expect(calendar.props.style).toBe(styles.displayNone);
|
|
155
|
+
// Click Done
|
|
156
|
+
await act(async () => {
|
|
157
|
+
await viewButtonBottom.props.onRightClick();
|
|
158
|
+
});
|
|
159
|
+
expect(calendar.props.style).not.toBe(styles.displayNone);
|
|
160
|
+
// Click Done
|
|
161
|
+
await act(async () => {
|
|
162
|
+
await viewButtonBottom.props.onRightClick();
|
|
163
|
+
});
|
|
164
|
+
expect(mockSetStartDate).toBeCalled();
|
|
165
|
+
expect(mockSetEndDate).toBeCalled();
|
|
166
|
+
expect(modal.props.isVisible).toBeFalsy();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('test press Cancel', async () => {
|
|
170
|
+
await act(async () => {
|
|
171
|
+
tree = await create(wrapComponent(props));
|
|
172
|
+
});
|
|
173
|
+
const instance = tree.root;
|
|
174
|
+
const modal = instance.findByType(BottomSheet);
|
|
175
|
+
|
|
176
|
+
await openCalendar(instance);
|
|
177
|
+
expect(modal.props.isVisible).toBeTruthy();
|
|
178
|
+
|
|
179
|
+
const calendar = instance.find(
|
|
180
|
+
(el) => el.props.testID === TESTID.HISTORY_CALENDAR
|
|
181
|
+
);
|
|
182
|
+
const calendarHeader = instance.findByType(CalendarHeader);
|
|
183
|
+
const viewButtonBottom = instance.findByType(ViewButtonBottom);
|
|
184
|
+
// Click calendar title
|
|
185
|
+
await act(async () => {
|
|
186
|
+
await calendarHeader.props.onPressTitle();
|
|
187
|
+
});
|
|
188
|
+
expect(calendar.props.style).toBe(styles.displayNone);
|
|
189
|
+
// Click Cancel
|
|
190
|
+
await act(async () => {
|
|
191
|
+
await viewButtonBottom.props.onLeftClick();
|
|
192
|
+
});
|
|
193
|
+
expect(calendar.props.style).not.toBe(styles.displayNone);
|
|
194
|
+
// Click Cancel
|
|
195
|
+
await act(async () => {
|
|
196
|
+
await viewButtonBottom.props.onLeftClick();
|
|
197
|
+
});
|
|
198
|
+
expect(modal.props.isVisible).toBeFalsy();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('test calendar select date range', async () => {
|
|
202
|
+
await act(async () => {
|
|
203
|
+
tree = await create(wrapComponent(props));
|
|
204
|
+
});
|
|
205
|
+
const instance = tree.root;
|
|
206
|
+
|
|
207
|
+
await openCalendar(instance);
|
|
208
|
+
const calendar = instance.find(
|
|
209
|
+
(el) => el.props.testID === TESTID.HISTORY_CALENDAR
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const selectDate = async (date) => {
|
|
213
|
+
await act(async () => {
|
|
214
|
+
await calendar.props.onDayPress({ dateString: date });
|
|
215
|
+
});
|
|
216
|
+
};
|
|
217
|
+
await selectDate('2021-09-20');
|
|
218
|
+
await selectDate('2021-09-02');
|
|
219
|
+
await selectDate('2021-09-09');
|
|
220
|
+
await selectDate('2021-09-10');
|
|
221
|
+
await selectDate('2021-09-05');
|
|
222
|
+
await selectDate('2021-09-05');
|
|
223
|
+
await selectDate('2021-09-20');
|
|
224
|
+
const viewButtonBottom = instance.findByType(ViewButtonBottom);
|
|
225
|
+
await act(async () => {
|
|
226
|
+
await viewButtonBottom.props.onRightClick();
|
|
227
|
+
});
|
|
228
|
+
expect(mockSetStartDate).toBeCalledWith(moment('2021-09-10').valueOf());
|
|
229
|
+
expect(mockSetEndDate).toBeCalledWith(moment('2021-09-20').valueOf());
|
|
230
|
+
});
|
|
231
|
+
});
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import React, { memo, useCallback, useState, useMemo, useRef } from 'react';
|
|
2
|
+
import { View, TouchableOpacity } from 'react-native';
|
|
3
|
+
import { IconOutline } from '@ant-design/icons-react-native';
|
|
4
|
+
import { Calendar } from 'react-native-calendars';
|
|
5
|
+
import moment from 'moment';
|
|
6
|
+
|
|
7
|
+
import Text from '../../commons/Text';
|
|
8
|
+
import BottomSheet from '../../commons/BottomSheet';
|
|
9
|
+
import SelectMonth from './SelectMonth';
|
|
10
|
+
import CalendarHeader from './CalendarHeader';
|
|
11
|
+
import ViewButtonBottom from '../ViewButtonBottom';
|
|
12
|
+
import { useTranslations } from '../../hooks/Common/useTranslations';
|
|
13
|
+
|
|
14
|
+
import { Colors } from '../../configs';
|
|
15
|
+
import { TESTID } from '../../configs/Constants';
|
|
16
|
+
import styles from './styles';
|
|
17
|
+
|
|
18
|
+
const ThreeButtonHistory = memo(
|
|
19
|
+
({ startDate, endDate, setEndDate, setStartDate, setGroupBy }) => {
|
|
20
|
+
const t = useTranslations();
|
|
21
|
+
const calendarRef = useRef();
|
|
22
|
+
const [selectedIndex, setSelectedIndex] = useState(2);
|
|
23
|
+
const [isShowDate, setIsShowDate] = useState(false);
|
|
24
|
+
const [selectedStart, setSelectedStart] = useState(
|
|
25
|
+
moment(startDate).format('YYYY-MM-DD')
|
|
26
|
+
);
|
|
27
|
+
const [selectedEnd, setSelectedEnd] = useState(
|
|
28
|
+
moment(endDate).format('YYYY-MM-DD')
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const [showSelectMonth, setShowSelectMonth] = useState(false);
|
|
32
|
+
const [currentMonth, setCurrentMonth] = useState(moment());
|
|
33
|
+
|
|
34
|
+
const listItem = useMemo(
|
|
35
|
+
() => [
|
|
36
|
+
{
|
|
37
|
+
id: 1,
|
|
38
|
+
dateTitle: '7D',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 2,
|
|
42
|
+
dateTitle: '1M',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 3,
|
|
46
|
+
dateTitle: (
|
|
47
|
+
<View style={styles.paddingTop2}>
|
|
48
|
+
<IconOutline
|
|
49
|
+
name="calendar"
|
|
50
|
+
color={selectedIndex === 2 ? Colors.Primary : Colors.Gray8}
|
|
51
|
+
size={21}
|
|
52
|
+
/>
|
|
53
|
+
</View>
|
|
54
|
+
),
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
[selectedIndex]
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const onPressCancel = useCallback(() => {
|
|
61
|
+
if (showSelectMonth) {
|
|
62
|
+
setShowSelectMonth(false);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
setIsShowDate(false);
|
|
66
|
+
}, [showSelectMonth, setShowSelectMonth]);
|
|
67
|
+
|
|
68
|
+
const onPressDone = useCallback(() => {
|
|
69
|
+
if (showSelectMonth) {
|
|
70
|
+
setShowSelectMonth(false);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (selectedStart === null || selectedEnd === null) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
setIsShowDate(false);
|
|
77
|
+
setStartDate(moment(selectedStart).valueOf());
|
|
78
|
+
setEndDate(moment(selectedEnd).valueOf());
|
|
79
|
+
}, [
|
|
80
|
+
setStartDate,
|
|
81
|
+
selectedStart,
|
|
82
|
+
setEndDate,
|
|
83
|
+
selectedEnd,
|
|
84
|
+
showSelectMonth,
|
|
85
|
+
setShowSelectMonth,
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
const onDayPress = useCallback(
|
|
89
|
+
(date) => {
|
|
90
|
+
const selectedDate = date.dateString;
|
|
91
|
+
if (selectedDate === selectedStart) {
|
|
92
|
+
setSelectedStart(null);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (selectedDate === selectedEnd) {
|
|
96
|
+
setSelectedEnd(null);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (selectedStart !== null && selectedEnd !== null) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (selectedEnd === null) {
|
|
103
|
+
setSelectedEnd(selectedDate);
|
|
104
|
+
} else {
|
|
105
|
+
if (moment(selectedDate).isAfter(selectedEnd, 'date')) {
|
|
106
|
+
setSelectedStart(selectedEnd);
|
|
107
|
+
setSelectedEnd(selectedDate);
|
|
108
|
+
} else {
|
|
109
|
+
setSelectedStart(selectedDate);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
[selectedStart, selectedEnd]
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const onPressItemButton = useCallback(
|
|
117
|
+
(index) => {
|
|
118
|
+
switch (index) {
|
|
119
|
+
case 0:
|
|
120
|
+
setGroupBy('week');
|
|
121
|
+
break;
|
|
122
|
+
case 1:
|
|
123
|
+
setGroupBy('month');
|
|
124
|
+
break;
|
|
125
|
+
case 2:
|
|
126
|
+
setGroupBy('date');
|
|
127
|
+
if (selectedIndex === 2) {
|
|
128
|
+
setIsShowDate(true);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
setSelectedIndex(index);
|
|
132
|
+
},
|
|
133
|
+
[selectedIndex, setGroupBy]
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const ItemButton = memo(
|
|
137
|
+
({ dateTitle, onPress, isSelected }) => {
|
|
138
|
+
return (
|
|
139
|
+
<TouchableOpacity
|
|
140
|
+
onPress={onPress}
|
|
141
|
+
style={[styles.buttonDate, isSelected && styles.buttonDateActive]}
|
|
142
|
+
testID={TESTID.HISTORY_BUTTON}
|
|
143
|
+
>
|
|
144
|
+
<View>
|
|
145
|
+
<Text semibold color={isSelected && Colors.Primary}>
|
|
146
|
+
{dateTitle}
|
|
147
|
+
</Text>
|
|
148
|
+
</View>
|
|
149
|
+
</TouchableOpacity>
|
|
150
|
+
);
|
|
151
|
+
},
|
|
152
|
+
[selectedIndex]
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const onPressCalendarTitle = useCallback(() => {
|
|
156
|
+
setShowSelectMonth((state) => !state);
|
|
157
|
+
}, [setShowSelectMonth]);
|
|
158
|
+
|
|
159
|
+
const onAddMonth = useCallback(() => {
|
|
160
|
+
setCurrentMonth((oldMonth) => {
|
|
161
|
+
const newMonth = moment(oldMonth).add(1, 'months');
|
|
162
|
+
if (newMonth.isAfter(moment(), 'month')) {
|
|
163
|
+
return oldMonth;
|
|
164
|
+
}
|
|
165
|
+
calendarRef?.current?.addMonth(1);
|
|
166
|
+
return newMonth;
|
|
167
|
+
});
|
|
168
|
+
}, []);
|
|
169
|
+
|
|
170
|
+
const onSubtractMonth = useCallback(() => {
|
|
171
|
+
setCurrentMonth((oldMonth) => moment(oldMonth).add(-1, 'months'));
|
|
172
|
+
calendarRef?.current?.addMonth(-1);
|
|
173
|
+
}, []);
|
|
174
|
+
|
|
175
|
+
const onMonthSelected = useCallback(
|
|
176
|
+
(month) => {
|
|
177
|
+
setCurrentMonth((oldMonth) => {
|
|
178
|
+
const monthDiff = month.month() - oldMonth.month();
|
|
179
|
+
calendarRef?.current?.addMonth(monthDiff);
|
|
180
|
+
return month;
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
[setCurrentMonth]
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const markedDates = useMemo(() => {
|
|
187
|
+
const dates = {
|
|
188
|
+
[selectedStart]: {
|
|
189
|
+
selected: true,
|
|
190
|
+
selectedColor: Colors.Primary,
|
|
191
|
+
selectedTextColor: Colors.White,
|
|
192
|
+
},
|
|
193
|
+
[selectedEnd]: {
|
|
194
|
+
selected: true,
|
|
195
|
+
selectedColor: Colors.Primary,
|
|
196
|
+
selectedTextColor: Colors.White,
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
const currDate = moment(selectedStart).startOf('day');
|
|
200
|
+
const lastDate = moment(selectedEnd).startOf('day');
|
|
201
|
+
|
|
202
|
+
while (currDate.add(1, 'days').diff(lastDate) < 0) {
|
|
203
|
+
dates[[moment(currDate).format('YYYY-MM-DD')]] = {
|
|
204
|
+
customStyles: {
|
|
205
|
+
container: {
|
|
206
|
+
borderRadius: 0,
|
|
207
|
+
width: 60,
|
|
208
|
+
backgroundColor: Colors.Gray4,
|
|
209
|
+
},
|
|
210
|
+
text: {
|
|
211
|
+
color: Colors.Black,
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
selected: true,
|
|
215
|
+
disableTouchEvent: true,
|
|
216
|
+
color: Colors.Gray4,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return dates;
|
|
220
|
+
}, [selectedStart, selectedEnd]);
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<>
|
|
224
|
+
<View style={styles.historyTitleRight}>
|
|
225
|
+
{listItem &&
|
|
226
|
+
listItem.map((item, index) => {
|
|
227
|
+
return (
|
|
228
|
+
<ItemButton
|
|
229
|
+
key={index}
|
|
230
|
+
dateTitle={item.dateTitle}
|
|
231
|
+
onPress={() => onPressItemButton(index)}
|
|
232
|
+
isSelected={selectedIndex === index}
|
|
233
|
+
/>
|
|
234
|
+
);
|
|
235
|
+
})}
|
|
236
|
+
</View>
|
|
237
|
+
<BottomSheet
|
|
238
|
+
isVisible={isShowDate}
|
|
239
|
+
onBackdropPress={onPressCancel}
|
|
240
|
+
style={styles.modal}
|
|
241
|
+
>
|
|
242
|
+
<View style={styles.calendar}>
|
|
243
|
+
<CalendarHeader
|
|
244
|
+
currentMonth={currentMonth}
|
|
245
|
+
onAddMonth={onAddMonth}
|
|
246
|
+
onSubtractMonth={onSubtractMonth}
|
|
247
|
+
onPressTitle={onPressCalendarTitle}
|
|
248
|
+
/>
|
|
249
|
+
{showSelectMonth && (
|
|
250
|
+
<SelectMonth
|
|
251
|
+
selectedMonth={currentMonth}
|
|
252
|
+
setSelectedMonth={onMonthSelected}
|
|
253
|
+
/>
|
|
254
|
+
)}
|
|
255
|
+
<Calendar
|
|
256
|
+
ref={calendarRef}
|
|
257
|
+
current={currentMonth.format('YYYY-MM-DD')}
|
|
258
|
+
style={showSelectMonth && styles.displayNone}
|
|
259
|
+
markingType={'custom'}
|
|
260
|
+
onDayPress={onDayPress}
|
|
261
|
+
maxDate={moment().format('YYYY-MM-DD')}
|
|
262
|
+
markedDates={markedDates}
|
|
263
|
+
hideArrows={true}
|
|
264
|
+
renderHeader={() => <></>}
|
|
265
|
+
testID={TESTID.HISTORY_CALENDAR}
|
|
266
|
+
/>
|
|
267
|
+
<View style={styles.separator} />
|
|
268
|
+
<ViewButtonBottom
|
|
269
|
+
leftTitle={t('cancel')}
|
|
270
|
+
onLeftClick={onPressCancel}
|
|
271
|
+
rightTitle={t('done')}
|
|
272
|
+
onRightClick={onPressDone}
|
|
273
|
+
/>
|
|
274
|
+
</View>
|
|
275
|
+
</BottomSheet>
|
|
276
|
+
</>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
export default ThreeButtonHistory;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
import { isIphoneX } from 'react-native-iphone-x-helper';
|
|
3
|
+
|
|
4
|
+
import { Colors } from '../../configs';
|
|
5
|
+
|
|
6
|
+
export default StyleSheet.create({
|
|
7
|
+
historyTitle: {
|
|
8
|
+
flex: 1,
|
|
9
|
+
flexDirection: 'row',
|
|
10
|
+
justifyContent: 'space-between',
|
|
11
|
+
},
|
|
12
|
+
historyTitleRight: {
|
|
13
|
+
flexDirection: 'row',
|
|
14
|
+
backgroundColor: Colors.Gray4,
|
|
15
|
+
borderRadius: 5,
|
|
16
|
+
paddingHorizontal: 2,
|
|
17
|
+
},
|
|
18
|
+
buttonDate: {
|
|
19
|
+
alignItems: 'center',
|
|
20
|
+
marginVertical: 2,
|
|
21
|
+
borderRadius: 5,
|
|
22
|
+
paddingHorizontal: 8,
|
|
23
|
+
},
|
|
24
|
+
buttonDateActive: {
|
|
25
|
+
backgroundColor: Colors.White,
|
|
26
|
+
},
|
|
27
|
+
modal: {
|
|
28
|
+
margin: 0,
|
|
29
|
+
padding: 0,
|
|
30
|
+
},
|
|
31
|
+
calendar: {
|
|
32
|
+
position: 'absolute',
|
|
33
|
+
bottom: 0,
|
|
34
|
+
left: 0,
|
|
35
|
+
right: 0,
|
|
36
|
+
borderTopLeftRadius: 10,
|
|
37
|
+
borderTopRightRadius: 10,
|
|
38
|
+
overflow: 'hidden',
|
|
39
|
+
backgroundColor: Colors.White,
|
|
40
|
+
},
|
|
41
|
+
separator: {
|
|
42
|
+
height: 1,
|
|
43
|
+
backgroundColor: Colors.Gray3,
|
|
44
|
+
marginTop: 5,
|
|
45
|
+
},
|
|
46
|
+
wrapBottomButton: {
|
|
47
|
+
height: 56 + (isIphoneX() ? 10 : 0),
|
|
48
|
+
justifyContent: 'space-between',
|
|
49
|
+
alignItems: 'center',
|
|
50
|
+
paddingHorizontal: 80,
|
|
51
|
+
flexDirection: 'row',
|
|
52
|
+
marginTop: -10,
|
|
53
|
+
},
|
|
54
|
+
button: {
|
|
55
|
+
height: 40,
|
|
56
|
+
justifyContent: 'center',
|
|
57
|
+
alignItems: 'center',
|
|
58
|
+
},
|
|
59
|
+
paddingTop2: {
|
|
60
|
+
paddingTop: 2,
|
|
61
|
+
},
|
|
62
|
+
displayNone: {
|
|
63
|
+
display: 'none',
|
|
64
|
+
},
|
|
65
|
+
});
|
package/src/configs/Constants.js
CHANGED
|
@@ -568,6 +568,10 @@ export const TESTID = {
|
|
|
568
568
|
// ActivityLog
|
|
569
569
|
FILTER_BUTTON: 'FILTER_BUTTON',
|
|
570
570
|
ITEM_USER_FILTER: 'ITEM_USER_FILTER',
|
|
571
|
+
|
|
572
|
+
// ThreeButtonHistory
|
|
573
|
+
HISTORY_BUTTON: 'HISTORY_BUTTON',
|
|
574
|
+
HISTORY_CALENDAR: 'HISTORY_CALENDAR',
|
|
571
575
|
};
|
|
572
576
|
|
|
573
577
|
export const NOTIFICATION_TYPES = {
|
|
@@ -108,7 +108,16 @@ describe('test AddNewOneTap', () => {
|
|
|
108
108
|
id: 1,
|
|
109
109
|
},
|
|
110
110
|
isCreateScriptSuccess: true,
|
|
111
|
-
automate: {
|
|
111
|
+
automate: {
|
|
112
|
+
id: 1,
|
|
113
|
+
script: {
|
|
114
|
+
id: 1,
|
|
115
|
+
name: 'William Miller',
|
|
116
|
+
},
|
|
117
|
+
type: 'one_tap',
|
|
118
|
+
unit: 1,
|
|
119
|
+
weekday_repeat: [],
|
|
120
|
+
},
|
|
112
121
|
isAutomateTab: false,
|
|
113
122
|
isMultiUnits: false,
|
|
114
123
|
});
|
|
@@ -46,10 +46,10 @@ const AddNewOneTap = memo(({ route }) => {
|
|
|
46
46
|
id: data.id,
|
|
47
47
|
name: name,
|
|
48
48
|
type: type,
|
|
49
|
-
automate,
|
|
49
|
+
automate: { ...automate, ...data },
|
|
50
50
|
havePermission: true,
|
|
51
51
|
isCreateScriptSuccess: true,
|
|
52
|
-
isAutomateTab:
|
|
52
|
+
isAutomateTab: isAutomateTab,
|
|
53
53
|
isMultiUnits,
|
|
54
54
|
});
|
|
55
55
|
}
|
|
@@ -59,8 +59,8 @@ const AddNewOneTap = memo(({ route }) => {
|
|
|
59
59
|
type,
|
|
60
60
|
name,
|
|
61
61
|
automateData,
|
|
62
|
-
automateId,
|
|
63
62
|
automate,
|
|
63
|
+
automateId,
|
|
64
64
|
navigate,
|
|
65
65
|
isAutomateTab,
|
|
66
66
|
]);
|
|
@@ -28,7 +28,7 @@ test('test select', async () => {
|
|
|
28
28
|
await act(async () => {
|
|
29
29
|
await items[0].props.onPress();
|
|
30
30
|
});
|
|
31
|
-
expect(mockSetWeekday).toBeCalledWith(['
|
|
31
|
+
expect(mockSetWeekday).toBeCalledWith(['1']);
|
|
32
32
|
|
|
33
33
|
mockSetWeekday.mockClear();
|
|
34
34
|
props = {
|
|
@@ -44,5 +44,5 @@ test('test select', async () => {
|
|
|
44
44
|
await act(async () => {
|
|
45
45
|
await items[0].props.onPress();
|
|
46
46
|
});
|
|
47
|
-
expect(mockSetWeekday).toBeCalledWith([]);
|
|
47
|
+
expect(mockSetWeekday).toBeCalledWith(['0', '1']);
|
|
48
48
|
});
|
|
@@ -141,13 +141,20 @@ const ThreePhasePowerConsumption = memo(({ unit, summary, summaryDetail }) => {
|
|
|
141
141
|
moment().subtract(7, 'days').valueOf()
|
|
142
142
|
);
|
|
143
143
|
const [endDate, setEndDate] = useState(moment().valueOf());
|
|
144
|
+
const [groupBy, setGroupBy] = useState('date');
|
|
144
145
|
const [getData, setData] = useState({});
|
|
146
|
+
const [chartConfig, setChartConfig] = useState({
|
|
147
|
+
unit: 'kWh',
|
|
148
|
+
price: '',
|
|
149
|
+
});
|
|
150
|
+
|
|
145
151
|
useEffect(() => {
|
|
146
152
|
const fetchData = async () => {
|
|
147
153
|
let params = new URLSearchParams();
|
|
148
154
|
params.append('config', listConfigs.total_power);
|
|
149
|
-
params.append('
|
|
150
|
-
params.append('
|
|
155
|
+
params.append('group_by', groupBy);
|
|
156
|
+
params.append('date_from', moment(startDate).format('YYYY-MM-DD'));
|
|
157
|
+
params.append('date_to', moment(endDate).format('YYYY-MM-DD'));
|
|
151
158
|
const { success, data } = await axiosGet(
|
|
152
159
|
API.POWER_CONSUME.DISPLAY_HISTORY,
|
|
153
160
|
{
|
|
@@ -161,7 +168,7 @@ const ThreePhasePowerConsumption = memo(({ unit, summary, summaryDetail }) => {
|
|
|
161
168
|
if (listConfigs?.total_power) {
|
|
162
169
|
fetchData();
|
|
163
170
|
}
|
|
164
|
-
}, [startDate, endDate, listConfigs]);
|
|
171
|
+
}, [startDate, endDate, listConfigs, groupBy]);
|
|
165
172
|
return (
|
|
166
173
|
<>
|
|
167
174
|
<Section type={'border'}>
|
|
@@ -239,13 +246,18 @@ const ThreePhasePowerConsumption = memo(({ unit, summary, summaryDetail }) => {
|
|
|
239
246
|
<PMSensorIndicatior data={dataTotal} style={styles.styleTotalPower} />
|
|
240
247
|
{!!getData?.length && (
|
|
241
248
|
<HistoryChart
|
|
242
|
-
unit={'kWh'}
|
|
243
249
|
datas={getData}
|
|
250
|
+
chartConfig={chartConfig}
|
|
251
|
+
setChartConfig={setChartConfig}
|
|
244
252
|
formatType={'date'}
|
|
245
253
|
startDate={startDate}
|
|
246
254
|
setEndDate={setEndDate}
|
|
247
255
|
setStartDate={setStartDate}
|
|
248
|
-
|
|
256
|
+
setGroupBy={setGroupBy}
|
|
257
|
+
configuration={{
|
|
258
|
+
type: 'horizontal_bar_chart',
|
|
259
|
+
config: 'power_consumption',
|
|
260
|
+
}}
|
|
249
261
|
/>
|
|
250
262
|
)}
|
|
251
263
|
</Section>
|
|
@@ -81,13 +81,20 @@ const PowerConsumption = memo(({ summaryDetail }) => {
|
|
|
81
81
|
moment().subtract(7, 'days').valueOf()
|
|
82
82
|
);
|
|
83
83
|
const [endDate, setEndDate] = useState(moment().valueOf());
|
|
84
|
-
const [
|
|
84
|
+
const [groupBy, setGroupBy] = useState('date');
|
|
85
|
+
const [getData, setData] = useState([]);
|
|
86
|
+
const [chartConfig, setChartConfig] = useState({
|
|
87
|
+
unit: 'kWh',
|
|
88
|
+
price: '',
|
|
89
|
+
});
|
|
90
|
+
|
|
85
91
|
useEffect(() => {
|
|
86
92
|
const fetchData = async () => {
|
|
87
93
|
let params = new URLSearchParams();
|
|
88
94
|
params.append('config', listConfigs.total_power);
|
|
89
|
-
params.append('
|
|
90
|
-
params.append('
|
|
95
|
+
params.append('group_by', groupBy);
|
|
96
|
+
params.append('date_from', moment(startDate).format('YYYY-MM-DD'));
|
|
97
|
+
params.append('date_to', moment(endDate).format('YYYY-MM-DD'));
|
|
91
98
|
const { success, data } = await axiosGet(
|
|
92
99
|
API.POWER_CONSUME.DISPLAY_HISTORY(),
|
|
93
100
|
{
|
|
@@ -101,7 +108,7 @@ const PowerConsumption = memo(({ summaryDetail }) => {
|
|
|
101
108
|
if (listConfigs?.total_power) {
|
|
102
109
|
fetchData();
|
|
103
110
|
}
|
|
104
|
-
}, [startDate, endDate, listConfigs]);
|
|
111
|
+
}, [startDate, endDate, listConfigs, groupBy]);
|
|
105
112
|
return (
|
|
106
113
|
<>
|
|
107
114
|
<Section type={'border'}>
|
|
@@ -145,13 +152,19 @@ const PowerConsumption = memo(({ summaryDetail }) => {
|
|
|
145
152
|
<PMSensorIndicatior data={dataTotal} style={styles.styleTotalPower} />
|
|
146
153
|
{!!getData?.length && (
|
|
147
154
|
<HistoryChart
|
|
148
|
-
unit={'kWh'}
|
|
149
155
|
datas={getData}
|
|
156
|
+
chartConfig={chartConfig}
|
|
157
|
+
setChartConfig={setChartConfig}
|
|
150
158
|
formatType={'date'}
|
|
151
159
|
startDate={startDate}
|
|
160
|
+
endDate={endDate}
|
|
152
161
|
setEndDate={setEndDate}
|
|
153
162
|
setStartDate={setStartDate}
|
|
154
|
-
|
|
163
|
+
setGroupBy={setGroupBy}
|
|
164
|
+
configuration={{
|
|
165
|
+
type: 'horizontal_bar_chart',
|
|
166
|
+
config: 'power_consumption',
|
|
167
|
+
}}
|
|
155
168
|
/>
|
|
156
169
|
)}
|
|
157
170
|
</Section>
|
|
@@ -133,14 +133,10 @@ const UnitSummary = memo(({ route }) => {
|
|
|
133
133
|
export default UnitSummary;
|
|
134
134
|
|
|
135
135
|
const styles = StyleSheet.create({
|
|
136
|
-
scrollView: {
|
|
137
|
-
flex: 1,
|
|
138
|
-
backgroundColor: Colors.Gray2,
|
|
139
|
-
paddingTop: 50,
|
|
140
|
-
},
|
|
141
136
|
container: {
|
|
142
137
|
flex: 1,
|
|
143
138
|
backgroundColor: Colors.Gray2,
|
|
139
|
+
paddingBottom: 60,
|
|
144
140
|
},
|
|
145
141
|
textHeader: {
|
|
146
142
|
color: Colors.Gray9,
|
|
@@ -379,6 +379,9 @@
|
|
|
379
379
|
"saved_vehicle": "Saved Vehicle",
|
|
380
380
|
"set_a_number": "Set a number",
|
|
381
381
|
"text_total_power_consumption": "Total Power Consumption",
|
|
382
|
+
"input_price_to_calculate_electricity_cost": "Input price to calculate electricity cost",
|
|
383
|
+
"calculate_cost": "Calculate cost",
|
|
384
|
+
"total_power_price": "Total power price:",
|
|
382
385
|
"smart_parking": "Smart Parking",
|
|
383
386
|
"smart_parking_add_destination": "I would like to park near...",
|
|
384
387
|
"see_nearby_parking": "See nearby parking areas",
|
|
@@ -412,6 +412,9 @@
|
|
|
412
412
|
"save_this_vehicle": "Lưu và đặt phương tiện mặc định",
|
|
413
413
|
"saved_vehicle": "Phương tiện đã lưu",
|
|
414
414
|
"text_total_power_consumption": "Tổng điện năng tiêu thụ",
|
|
415
|
+
"input_price_to_calculate_electricity_cost": "Nhập số tiền để tính giá tiền điện đã sử dụng",
|
|
416
|
+
"calculate_cost": "Tính giá điện",
|
|
417
|
+
"total_power_price": "Tổng giá điện:",
|
|
415
418
|
"smart_parking": "Smart Parking",
|
|
416
419
|
"smart_parking_add_destination": "Tôi muốn đỗ xe gần...",
|
|
417
420
|
"add_card": "Thêm thẻ",
|