@eohjsc/react-native-smart-city 0.2.47 → 0.2.51
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 +24 -4
- package/src/commons/Dashboard/MyPinnedSharedUnit/styles.js +1 -1
- 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/ActivityLog/__test__/ItemLog.test.js +0 -20
- package/src/screens/AddNewOneTap/__test__/AddNewOneTap.test.js +10 -1
- package/src/screens/AddNewOneTap/index.js +3 -3
- package/src/screens/ScriptDetail/index.js +18 -9
- package/src/screens/SetSchedule/__test__/SelectWeekday.test.js +2 -2
- package/src/screens/SetSchedule/components/SelectWeekday.js +13 -7
- package/src/screens/SubUnit/AddSubUnit.js +2 -2
- package/src/screens/SubUnit/__test__/AddSubUnit.test.js +1 -0
- package/src/screens/Unit/SmartAccount.js +1 -1
- 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.51",
|
|
5
5
|
"description": "TODO",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
@@ -39,12 +39,16 @@
|
|
|
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": "
|
|
42
|
+
"postinstall": "npx husky install",
|
|
43
43
|
"example": "yarn --cwd example",
|
|
44
44
|
"pods": "cd example && pod-install --quiet",
|
|
45
45
|
"bootstrap": "yarn example && yarn && yarn pods",
|
|
46
46
|
"build": "sync-files ./src ../EohMobile/node_modules/@eohjsc/react-native-smart-city/src",
|
|
47
|
-
"watch": "sync-files --watch ./src ../EohMobile/node_modules/@eohjsc/react-native-smart-city/src"
|
|
47
|
+
"watch": "sync-files --watch ./src ../EohMobile/node_modules/@eohjsc/react-native-smart-city/src",
|
|
48
|
+
"test:code-formatter": "prettier src/**/**/*.js --write",
|
|
49
|
+
"test:code-formatter-step-1": "prettier --check src/**/*.js",
|
|
50
|
+
"test:code-formatter-step-2": "prettier --check src/**/**/*.js",
|
|
51
|
+
"test:code-formatter-step-3": "prettier --check src/**/**/**/*.js"
|
|
48
52
|
},
|
|
49
53
|
"repository": {
|
|
50
54
|
"type": "git",
|
|
@@ -82,7 +86,7 @@
|
|
|
82
86
|
"eslint-plugin-react": "^7.21.5",
|
|
83
87
|
"eslint-plugin-react-native": "^3.10.0",
|
|
84
88
|
"factory-girl": "^5.0.4",
|
|
85
|
-
"husky": "^2.
|
|
89
|
+
"husky": "^2.7.0",
|
|
86
90
|
"jest": "^26.6.3",
|
|
87
91
|
"jest-circus": "^26.6.3",
|
|
88
92
|
"jetifier": "^1.6.6",
|
|
@@ -159,6 +163,7 @@
|
|
|
159
163
|
"react-native-gesture-handler": "^1.7.0",
|
|
160
164
|
"react-native-get-location": "^2.0.0",
|
|
161
165
|
"react-native-image-picker": "^3.1.4",
|
|
166
|
+
"react-native-image-resizer": "^1.4.5",
|
|
162
167
|
"react-native-input-credit-card": "^0.5.5",
|
|
163
168
|
"react-native-iphone-x-helper": "^1.2.1",
|
|
164
169
|
"react-native-keyboard-aware-scroll-view": "^0.9.2",
|
|
@@ -201,6 +206,21 @@
|
|
|
201
206
|
"validator": "^13.1.1",
|
|
202
207
|
"victory-native": "^35.0.1"
|
|
203
208
|
},
|
|
209
|
+
"husky": {
|
|
210
|
+
"hooks": {
|
|
211
|
+
"pre-commit": "lint-staged",
|
|
212
|
+
"pre-push": "yarn jest"
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
"lint-staged": {
|
|
216
|
+
"*.{js, ts}": [
|
|
217
|
+
"yarn lint",
|
|
218
|
+
"yarn test:code-formatter",
|
|
219
|
+
"yarn test:code-formatter-step-1",
|
|
220
|
+
"yarn test:code-formatter-step-2",
|
|
221
|
+
"yarn test:code-formatter-step-3"
|
|
222
|
+
]
|
|
223
|
+
},
|
|
204
224
|
"bugs": {
|
|
205
225
|
"url": "https://github.com/github_account/react-native-smart-city/issues"
|
|
206
226
|
},
|
|
@@ -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
|
+
});
|