@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.
Files changed (29) hide show
  1. package/README.md +9 -0
  2. package/package.json +24 -4
  3. package/src/commons/Dashboard/MyPinnedSharedUnit/styles.js +1 -1
  4. package/src/commons/Device/HistoryChart.js +133 -14
  5. package/src/commons/Device/HorizontalBarChart.js +30 -3
  6. package/src/commons/Device/LinearChart.js +2 -7
  7. package/src/commons/ThreeButtonHistory/CalendarHeader.js +35 -0
  8. package/src/commons/ThreeButtonHistory/CalendarHeaderStyles.js +17 -0
  9. package/src/commons/ThreeButtonHistory/SelectMonth.js +53 -0
  10. package/src/commons/ThreeButtonHistory/SelectMonthStyles.js +29 -0
  11. package/src/commons/ThreeButtonHistory/__test__/SelectMonth.test.js +37 -0
  12. package/src/commons/ThreeButtonHistory/__test__/ThreeButtonHistory.test.js +231 -0
  13. package/src/commons/ThreeButtonHistory/index.js +281 -0
  14. package/src/commons/ThreeButtonHistory/styles.js +65 -0
  15. package/src/configs/Constants.js +4 -0
  16. package/src/screens/ActivityLog/__test__/ItemLog.test.js +0 -20
  17. package/src/screens/AddNewOneTap/__test__/AddNewOneTap.test.js +10 -1
  18. package/src/screens/AddNewOneTap/index.js +3 -3
  19. package/src/screens/ScriptDetail/index.js +18 -9
  20. package/src/screens/SetSchedule/__test__/SelectWeekday.test.js +2 -2
  21. package/src/screens/SetSchedule/components/SelectWeekday.js +13 -7
  22. package/src/screens/SubUnit/AddSubUnit.js +2 -2
  23. package/src/screens/SubUnit/__test__/AddSubUnit.test.js +1 -0
  24. package/src/screens/Unit/SmartAccount.js +1 -1
  25. package/src/screens/UnitSummary/components/3PPowerConsumption/index.js +17 -5
  26. package/src/screens/UnitSummary/components/PowerConsumption/index.js +19 -6
  27. package/src/screens/UnitSummary/index.js +1 -5
  28. package/src/utils/I18n/translations/en.json +3 -0
  29. 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.47",
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": "patch-package",
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.4.1",
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,5 +1,5 @@
1
1
  import { Dimensions, StyleSheet } from 'react-native';
2
- import { Colors } from 'configs';
2
+ import { Colors } from '../../../configs';
3
3
 
4
4
  const { width: widthScreen } = Dimensions.get('window');
5
5
 
@@ -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
- unit,
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 unit={unit} datas={datas} />;
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
- <Text size={20} semibold color={Colors.Gray9}>
149
- {t('history')}
150
- </Text>
151
- <DateTimeRangeChange
152
- startTime={eventPicker.startTime}
153
- onStart={onStart}
154
- onEnd={onEnd}
155
- endTime={eventPicker.endTime}
156
- date={dateNow}
157
- formatType={formatType}
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, unit }) => {
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: datas[0].measure,
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
- format: `{point.y} ${unit}`,
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
- let hours = ('0' + time.getHours()).slice(-2);
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
+ });