@eohjsc/react-native-smart-city 0.5.9 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eohjsc/react-native-smart-city",
3
3
  "title": "React Native Smart Home",
4
- "version": "0.5.9",
4
+ "version": "0.6.0",
5
5
  "description": "TODO",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -123,7 +123,7 @@ const HorizontalBarChart = memo(({ datas, config }) => {
123
123
  y: item.y,
124
124
  };
125
125
  });
126
- const dataX = (datas[0].data || []).map((item) => item.x);
126
+ const dataX = (datas[0]?.data || []).map((item) => item.x);
127
127
  const maxY = getMaxValueIndex(dataY);
128
128
  if (!isEmpty(maxY.max)) {
129
129
  dataY.splice(maxY._index, 1, { ...maxY.max, color: Colors.Primary });
@@ -0,0 +1,154 @@
1
+ import React, { memo, useCallback, useState, useMemo, useEffect } from 'react';
2
+ import { StyleSheet, View } from 'react-native';
3
+ import moment from 'moment';
4
+ import { useTranslations } from '../../hooks/Common/useTranslations';
5
+
6
+ import { Colors } from '../../configs';
7
+ import Text from '../Text';
8
+ import Button from '../Button';
9
+ import CurrencyInput from '../Form/CurrencyInput';
10
+ import DateTimeRangeChange from '../DateTimeRangeChange';
11
+ import HorizontalBarChart from './HorizontalBarChart';
12
+ import ChartAggregationOption from '../ChartAggregationOption';
13
+ import { formatMoney } from '../../utils/Utils';
14
+
15
+ const PowerConsumptionChart = memo(
16
+ ({
17
+ datas,
18
+ chartConfig,
19
+ setChartConfig,
20
+ style,
21
+ groupBy,
22
+ setGroupBy,
23
+ onChangeDate,
24
+ }) => {
25
+ const t = useTranslations();
26
+ const [price, setPrice] = useState(null);
27
+
28
+ const onCalculateCost = useCallback(() => {
29
+ setChartConfig((config) => ({
30
+ ...config,
31
+ price,
32
+ }));
33
+ }, [setChartConfig, price]);
34
+
35
+ const totalPrice = useMemo(() => {
36
+ const { price: chartPrice } = chartConfig;
37
+ if (chartPrice === '' || isNaN(chartPrice)) {
38
+ return null;
39
+ }
40
+ const sum = datas[0].data.reduce((a, b) => a + b.y, 0);
41
+ const roundedSum = sum * chartPrice;
42
+ return roundedSum.toFixed();
43
+ }, [datas, chartConfig]);
44
+
45
+ const renderChart = useMemo(() => {
46
+ return <HorizontalBarChart datas={datas} config={chartConfig} />;
47
+ }, [chartConfig, datas]);
48
+
49
+ const [value, setValue] = useState([
50
+ moment().subtract(6, 'days'),
51
+ moment(),
52
+ ]);
53
+
54
+ useEffect(() => {
55
+ onChangeDate(value[0], value[1]);
56
+ }, [onChangeDate, value]);
57
+
58
+ const selectStart = (date) => {
59
+ setValue((state) => [date, state[1]]);
60
+ };
61
+ const selectEnd = (date) => {
62
+ setValue((state) => [state[0], date]);
63
+ };
64
+
65
+ return (
66
+ <View style={style}>
67
+ <View style={styles.historyView}>
68
+ <View style={styles.titleHistory}>
69
+ <Text size={20} semibold color={Colors.Gray9}>
70
+ {t('history')}
71
+ </Text>
72
+
73
+ <ChartAggregationOption groupBy={groupBy} setGroupBy={setGroupBy} />
74
+ </View>
75
+ {groupBy === 'date' && (
76
+ <DateTimeRangeChange
77
+ startTime={value[0]}
78
+ endTime={value[1]}
79
+ selectStart={selectStart}
80
+ selectEnd={selectEnd}
81
+ />
82
+ )}
83
+ </View>
84
+
85
+ <View style={styles.wrapCalculateCost}>
86
+ <Text type="H4">
87
+ {t('input_price_to_calculate_electricity_cost')}
88
+ </Text>
89
+ <View style={styles.row}>
90
+ <CurrencyInput
91
+ value={price}
92
+ onChange={setPrice}
93
+ minValue={0}
94
+ maxValue={100000000}
95
+ placeholder={'0'}
96
+ onSubmitEditing={onCalculateCost}
97
+ />
98
+ <Button
99
+ type="primary"
100
+ title={t('calculate_cost')}
101
+ onPress={onCalculateCost}
102
+ style={styles.buttonCalculate}
103
+ textSemiBold={false}
104
+ />
105
+ </View>
106
+ </View>
107
+
108
+ {renderChart}
109
+ {!!chartConfig.price && (
110
+ <Text type="H4">
111
+ {t('total_power_price')} <Text bold>{formatMoney(totalPrice)}</Text>
112
+ </Text>
113
+ )}
114
+ </View>
115
+ );
116
+ }
117
+ );
118
+
119
+ export default PowerConsumptionChart;
120
+
121
+ const styles = StyleSheet.create({
122
+ historyView: {
123
+ paddingTop: 16,
124
+ },
125
+ titleHistory: {
126
+ flexDirection: 'row',
127
+ alignItems: 'center',
128
+ justifyContent: 'space-between',
129
+ },
130
+ chartInfo: {
131
+ flexDirection: 'row',
132
+ justifyContent: 'center',
133
+ alignItems: 'center',
134
+ },
135
+ chartContainer: {
136
+ marginLeft: -8,
137
+ },
138
+ chart: {
139
+ height: 300,
140
+ },
141
+ wrapCalculateCost: {
142
+ marginTop: 12,
143
+ },
144
+ row: {
145
+ flexDirection: 'row',
146
+ alignItems: 'center',
147
+ marginTop: 8,
148
+ },
149
+ buttonCalculate: {
150
+ width: null,
151
+ flex: null,
152
+ paddingHorizontal: 12,
153
+ },
154
+ });
@@ -1,8 +1,7 @@
1
- import React, { useCallback, useEffect, useState } from 'react';
1
+ import { useCallback } from 'react';
2
2
  import moment from 'moment';
3
3
 
4
4
  import { API } from '../../../configs';
5
- import HistoryChart from '../../../commons/Device/HistoryChart';
6
5
  import { axiosGet } from '../../../utils/Apis/axios';
7
6
  import { getPusher } from '../../../utils/Pusher';
8
7
 
@@ -39,11 +38,12 @@ export const useFetchConfigHistory = (
39
38
  });
40
39
 
41
40
  const timezone = moment().utcOffset();
42
- startDate = startDate.subtract(timezone, 'minutes');
43
- endDate = endDate.subtract(timezone, 'minutes');
44
41
 
45
- params.append('date_from', startDate.format('YYYY-MM-DDTHH:mm:ss'));
46
- params.append('date_to', endDate.format('YYYY-MM-DDTHH:mm:ss'));
42
+ const date_from = moment(startDate).subtract(timezone, 'minutes');
43
+ const date_to = moment(endDate).subtract(timezone, 'minutes');
44
+
45
+ params.append('date_from', date_from.format('YYYY-MM-DDTHH:mm:ss'));
46
+ params.append('date_to', date_to.format('YYYY-MM-DDTHH:mm:ss'));
47
47
 
48
48
  const { success, data } = await axiosGet(endpoint, {
49
49
  params,
@@ -122,58 +122,3 @@ export const updateConfigChart = async (
122
122
  }
123
123
  });
124
124
  };
125
-
126
- let timeoutId;
127
-
128
- const ConfigHistoryChart = ({ configs }) => {
129
- const [chartData, setChartData] = useState(configs);
130
- const [startDate, setStartDate] = useState(
131
- moment().subtract(1, 'days').valueOf()
132
- );
133
- const [endDate, setEndDate] = useState(moment().valueOf());
134
-
135
- useEffect(() => {
136
- const fetchData = async () => {
137
- let params = new URLSearchParams();
138
- let configuration = configs.filter((item) => item.id);
139
- configuration.map((item) => {
140
- params.append('configs', item.id);
141
- });
142
- params.append(
143
- 'date_from',
144
- moment(startDate).utc().format('YYYY-MM-DD HH:mm:ss')
145
- );
146
- params.append(
147
- 'date_to',
148
- moment(endDate).utc().format('YYYY-MM-DD HH:mm:ss')
149
- );
150
- const { success, data } = await axiosGet(
151
- API.CONFIG.DISPLAY_HISTORY_V3(),
152
- {
153
- params,
154
- }
155
- );
156
- updateConfigChart(success, data, configuration, setChartData);
157
- };
158
-
159
- timeoutId = setTimeout(fetchData, 200);
160
- return () => {
161
- clearTimeout(timeoutId);
162
- };
163
- }, [startDate, endDate, configs]);
164
-
165
- if (!chartData.length) {
166
- return false;
167
- }
168
-
169
- return (
170
- <HistoryChart
171
- configuration={{ type: 'line_chart', date_format: 'DD.MM' }}
172
- datas={chartData}
173
- setStartDate={setStartDate}
174
- setEndDate={setEndDate}
175
- />
176
- );
177
- };
178
-
179
- export default ConfigHistoryChart;
@@ -5,20 +5,21 @@ import { axiosGet } from '../../utils/Apis/axios';
5
5
  import { API } from '../../configs';
6
6
  import { Action } from '../../context/actionType';
7
7
 
8
+ let timeoutId = null;
9
+
8
10
  const useDevicesStatus = (unit, devices) => {
9
11
  const { setAction } = useContext(SCContext);
10
12
  const isNetworkConnected = useSCContextSelector(
11
13
  (state) => state.app.isNetworkConnected
12
14
  );
13
15
  const isFocused = useIsFocused();
14
- const timeoutIdRef = useRef(null);
15
- const hasFetchedRef = useRef(false);
16
+ const hasFetched = useRef(false); // Track if data has been fetched
16
17
 
17
18
  const getDevicesStatus = useCallback(
18
19
  async (_unit, _devices) => {
19
- if (timeoutIdRef.current) {
20
- clearTimeout(timeoutIdRef.current);
21
- timeoutIdRef.current = null;
20
+ if (timeoutId) {
21
+ clearTimeout(timeoutId);
22
+ timeoutId = null;
22
23
  }
23
24
  const params = new URLSearchParams();
24
25
  _devices.forEach((device) => {
@@ -30,24 +31,18 @@ const useDevicesStatus = (unit, devices) => {
30
31
  params: params,
31
32
  }
32
33
  );
33
- if (success) {
34
- setAction(Action.SET_DEVICES_STATUS, data);
35
- }
36
- timeoutIdRef.current = setTimeout(
37
- () => getDevicesStatus(_unit, _devices),
38
- 10000
39
- );
34
+ success && setAction(Action.SET_DEVICES_STATUS, data);
35
+ timeoutId = setTimeout(() => getDevicesStatus(_unit, _devices), 10000);
36
+ // eslint-disable-next-line react-hooks/exhaustive-deps
40
37
  },
41
38
  [setAction]
42
39
  );
43
40
 
44
41
  useEffect(() => {
45
- if (
46
- !isFocused ||
47
- !devices?.length ||
48
- !isNetworkConnected ||
49
- hasFetchedRef.current
50
- ) {
42
+ if (!isFocused || !isNetworkConnected || hasFetched.current) {
43
+ return;
44
+ }
45
+ if (!devices?.length) {
51
46
  return;
52
47
  }
53
48
 
@@ -57,19 +52,25 @@ const useDevicesStatus = (unit, devices) => {
57
52
  if (!managedDevices.length) {
58
53
  return;
59
54
  }
55
+
60
56
  getDevicesStatus(unit, devices);
61
- hasFetchedRef.current = true;
57
+ hasFetched.current = true; // Mark as fetched
62
58
  return () => {
63
- if (timeoutIdRef.current) {
64
- clearTimeout(timeoutIdRef.current);
65
- timeoutIdRef.current = null;
59
+ if (timeoutId) {
60
+ clearTimeout(timeoutId);
61
+ timeoutId = null;
66
62
  }
67
63
  };
68
64
  }, [getDevicesStatus, unit, devices, isNetworkConnected, isFocused]);
69
65
 
70
66
  useEffect(() => {
67
+ // Reset the hasFetched flag when the component is no longer focused
71
68
  if (!isFocused) {
72
- hasFetchedRef.current = false;
69
+ hasFetched.current = false;
70
+ if (timeoutId) {
71
+ clearTimeout(timeoutId); // Clear timeout when losing focus
72
+ timeoutId = null;
73
+ }
73
74
  }
74
75
  }, [isFocused]);
75
76
  };
@@ -45,7 +45,7 @@ jest.mock('@react-navigation/native', () => {
45
45
 
46
46
  describe('Test SensorDisplayItem', () => {
47
47
  let tree;
48
- it('render DetailHistoryChart', async () => {
48
+ it('render visualChart', async () => {
49
49
  const item = {
50
50
  id: 10452,
51
51
  order: 0,
@@ -74,8 +74,8 @@ describe('Test SensorDisplayItem', () => {
74
74
  tree = await renderer.create(wrapComponent({ item, sensor }));
75
75
  });
76
76
  const instance = tree.root;
77
- const detailHistoryChart = instance.findAllByType(VisualChart);
78
- expect(detailHistoryChart).toHaveLength(1);
77
+ const visualChart = instance.findAllByType(VisualChart);
78
+ expect(visualChart).toHaveLength(1);
79
79
  });
80
80
 
81
81
  it('render ActionGroup', async () => {
@@ -20,7 +20,6 @@ import { useConfigGlobalState } from '../../../iot/states';
20
20
  import SmartIr from '../../../screens/SmartIr';
21
21
  import { useEvaluateValue } from '../hooks/useEvaluateValue';
22
22
  import styles from '../styles';
23
- import { DetailHistoryChart } from './DetailHistoryChart';
24
23
  import VisualChart from './VisualChart';
25
24
  import { standardizeCameraScreenSize } from '../../../utils/Utils';
26
25
  import { Device } from '../../../configs';
@@ -177,9 +176,6 @@ export const SensorDisplayItem = ({
177
176
  />
178
177
  );
179
178
  case 'history':
180
- if (configuration?.config === 'power_consumption') {
181
- return <DetailHistoryChart item={item} sensor={sensor} />;
182
- }
183
179
  return (
184
180
  <VisualChart
185
181
  item={item}
@@ -52,12 +52,14 @@ export default StyleSheet.create({
52
52
  },
53
53
  commonButton: {
54
54
  height: 40,
55
+ width: 80,
55
56
  justifyContent: 'center',
56
57
  alignItems: 'center',
57
58
  },
58
59
  row: {
59
60
  flexDirection: 'row',
60
61
  alignItems: 'center',
62
+ marginHorizontal: 20,
61
63
  },
62
64
  container: {
63
65
  position: 'absolute',
@@ -66,12 +68,8 @@ export default StyleSheet.create({
66
68
  alignItems: 'center',
67
69
  },
68
70
  textDate: {
69
- marginLeft: 32,
70
71
  marginRight: 10,
71
72
  },
72
- iconDate: {
73
- marginRight: 32,
74
- },
75
73
  timer: {
76
74
  height: 80,
77
75
  marginTop: 20,
@@ -1,12 +1,15 @@
1
- import React, { useEffect, useMemo, useRef, useState } from 'react';
1
+ import React, {
2
+ useEffect,
3
+ useMemo,
4
+ useRef,
5
+ useState,
6
+ useCallback,
7
+ } from 'react';
2
8
  import { View, Animated } from 'react-native';
3
9
  import { Constants, Colors } from '../../configs';
4
10
  import Text from '../../commons/Text';
5
11
  import styles from './Styles/TimerStyles';
6
12
 
7
- let time = -1;
8
- let isFirstTime = true;
9
-
10
13
  const Timer = ({
11
14
  width = Constants.width,
12
15
  onChangeValue,
@@ -29,70 +32,91 @@ const Timer = ({
29
32
  }) => {
30
33
  const scrollViewRef = useRef();
31
34
  const [scrollX] = useState(new Animated.Value(0));
35
+ const isFirstTime = useRef(true); // Use useRef to track the first render
32
36
 
33
- const snapSegment = segmentWidth + segmentSpacing;
34
- const spacerWidth = (width - segmentWidth) / 2;
35
- const timerWidth = width - segmentWidth + (maximum - minimum) * snapSegment;
37
+ const snapSegment = useMemo(
38
+ () => segmentWidth + segmentSpacing,
39
+ [segmentWidth, segmentSpacing]
40
+ );
41
+ const spacerWidth = useMemo(
42
+ () => (width - segmentWidth) / 2,
43
+ [width, segmentWidth]
44
+ );
45
+ const timerWidth = useMemo(
46
+ () => width - segmentWidth + (maximum - minimum) * snapSegment,
47
+ [width, segmentWidth, maximum, minimum, snapSegment]
48
+ );
36
49
 
37
50
  const renderTime = useMemo(() => {
51
+ let time = -1;
38
52
  const data = [...Array(maximum - minimum + 1).keys()].map(
39
53
  (i) => i + minimum
40
54
  );
55
+
41
56
  return (
42
57
  <View style={[styles.wrap, { width: timerWidth + segmentWidth }]}>
43
- <View
44
- style={{
45
- width: spacerWidth,
46
- }}
47
- />
48
- {data.map((i, index) => {
58
+ <View style={{ width: spacerWidth }} />
59
+ {data.map((i) => {
60
+ const isStep = i % step === 0;
61
+ if (isStep) {
62
+ time += 1;
63
+ }
49
64
  return (
50
65
  <View key={i}>
51
66
  <View
52
67
  style={{
53
- backgroundColor: i % step === 0 ? stepColor : normalColor,
54
- height: i % step === 0 ? stepHeight : normalHeight,
68
+ backgroundColor: isStep ? stepColor : normalColor,
69
+ height: isStep ? stepHeight : normalHeight,
55
70
  width: segmentWidth,
56
71
  marginRight: segmentSpacing,
57
72
  }}
58
73
  />
59
- {i % step === 0 && (
60
- <Text type="Label" color={Colors.Gray7} style={styles.time}>{`${
61
- time < 9 ? '0' + ++time : ++time
62
- }:00`}</Text>
74
+ {isStep && (
75
+ <Text type="Label" color={Colors.Gray7} style={styles.time}>
76
+ {`${time < 9 ? '0' + time : time}:00`}
77
+ </Text>
63
78
  )}
64
79
  </View>
65
80
  );
66
81
  })}
67
82
  </View>
68
83
  );
69
- // eslint-disable-next-line react-hooks/exhaustive-deps
70
- }, []);
84
+ }, [
85
+ maximum,
86
+ minimum,
87
+ step,
88
+ stepColor,
89
+ normalColor,
90
+ stepHeight,
91
+ normalHeight,
92
+ segmentWidth,
93
+ segmentSpacing,
94
+ timerWidth,
95
+ spacerWidth,
96
+ ]);
71
97
 
72
98
  useEffect(() => {
73
99
  const scrollListener = scrollX.addListener(({ value: timeValue }) => {
74
- !isFirstTime && onChangeValue && onChangeValue(timeValue, selected);
100
+ if (!isFirstTime.current) {
101
+ onChangeValue(timeValue, selected);
102
+ }
75
103
  });
104
+
76
105
  return () => scrollX.removeListener(scrollListener);
77
- // eslint-disable-next-line react-hooks/exhaustive-deps
78
- }, [isFirstTime, selected]);
106
+ }, [scrollX, onChangeValue, selected]);
79
107
 
80
108
  useEffect(() => {
81
- return () => {
82
- time = -1;
83
- isFirstTime = true;
84
- };
85
- }, []);
109
+ const timeoutId = setTimeout(() => {
110
+ if (scrollViewRef.current) {
111
+ scrollViewRef.current.scrollTo({ x: value, animated: false });
112
+ }
113
+ isFirstTime.current = false; // Set to false after initial scroll
114
+ }, 400);
86
115
 
87
- useEffect(() => {
88
- if (isFirstTime) {
89
- const to = setTimeout(() => {
90
- scrollViewRef?.current.scrollTo({ x: value });
91
- clearTimeout(to);
92
- isFirstTime = false;
93
- }, 400);
94
- }
95
- }, [value, scrollViewRef]);
116
+ return () => clearTimeout(timeoutId);
117
+ }, [value]);
118
+
119
+ const handleScrollEndDrag = useCallback(onScrollEndDrag, [onScrollEndDrag]);
96
120
 
97
121
  return (
98
122
  <View>
@@ -104,17 +128,11 @@ const Timer = ({
104
128
  showsHorizontalScrollIndicator={false}
105
129
  scrollEventThrottle={16}
106
130
  onScroll={Animated.event(
107
- [
108
- {
109
- nativeEvent: {
110
- contentOffset: { x: scrollX },
111
- },
112
- },
113
- ],
131
+ [{ nativeEvent: { contentOffset: { x: scrollX } } }],
114
132
  { useNativeDriver: true }
115
133
  )}
116
- onScrollEndDrag={onScrollEndDrag}
117
- onMomentumScrollEnd={onScrollEndDrag}
134
+ onScrollEndDrag={handleScrollEndDrag}
135
+ onMomentumScrollEnd={handleScrollEndDrag}
118
136
  >
119
137
  {renderTime}
120
138
  </Animated.ScrollView>