@eohjsc/react-native-smart-city 0.5.7-rc1 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -2
- package/src/commons/ActionGroup/OnOffTemplate/index.js +5 -5
- package/src/commons/ActionGroup/TerminalBoxTemplate.js +4 -2
- package/src/commons/ActionGroup/TextBoxTemplate.js +3 -0
- package/src/commons/ActionGroup/__test__/OnOffButtonTemplate.test.js +1 -1
- package/src/commons/ActionGroup/__test__/OnOffTemplate.test.js +21 -13
- package/src/commons/ActionGroup/__test__/index.test.js +4 -2
- package/src/commons/DateTimeRangeChange/DateTimeButton.js +1 -1
- package/src/commons/Form/TextInput.js +2 -0
- package/src/commons/Highcharts/index.js +41 -41
- package/src/commons/SubUnit/ShortDetail.js +1 -1
- package/src/commons/SubUnit/__test__/ShortDetail.test.js +1 -0
- package/src/commons/UnitSummary/ConfigHistoryChart/index.js +3 -1
- package/src/configs/Constants.js +8 -0
- package/src/screens/Device/components/ChartWrapper.js +11 -4
- package/src/screens/Device/components/DonutChart.js +138 -0
- package/src/screens/Device/components/SensorDisplayItem.js +13 -21
- package/src/screens/Device/components/VisualChart.js +32 -8
- package/src/screens/UnitSummary/components/AirQuality/SegmentedRoundChart.js +5 -3
- package/src/screens/UnitSummary/components/UvIndex/SegmentedRoundChart.js +1 -1
- package/src/screens/UnitSummary/components/WaterQuality/index.js +0 -30
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.
|
|
4
|
+
"version": "0.5.8",
|
|
5
5
|
"description": "TODO",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
@@ -161,7 +161,6 @@
|
|
|
161
161
|
"react-native-bootsplash": "^2.2.5",
|
|
162
162
|
"react-native-calendars": "^1.1266.0",
|
|
163
163
|
"react-native-chart-kit": "^6.5.0",
|
|
164
|
-
"react-native-charts-wrapper": "^0.6.0",
|
|
165
164
|
"react-native-credit-card-input": "^0.4.1",
|
|
166
165
|
"react-native-crypto-aes-cbc": "^1.1.1",
|
|
167
166
|
"react-native-dash": "^0.0.11",
|
|
@@ -51,7 +51,7 @@ const OnOffTemplate = memo(({ item = {}, doAction, sensor = {} }) => {
|
|
|
51
51
|
const [isOn, setIsOn] = useState(false);
|
|
52
52
|
|
|
53
53
|
const realTrigger = useCallback(
|
|
54
|
-
async (action_data, config_value) => {
|
|
54
|
+
async (action_data, config_value, interrupted) => {
|
|
55
55
|
if (!action_data) {
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
@@ -66,7 +66,7 @@ const OnOffTemplate = memo(({ item = {}, doAction, sensor = {} }) => {
|
|
|
66
66
|
config_value: config_value,
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
|
-
await doAction(action_data, data);
|
|
69
|
+
await doAction(action_data, data, interrupted);
|
|
70
70
|
if (
|
|
71
71
|
is_managed_by_backend &&
|
|
72
72
|
config &&
|
|
@@ -87,15 +87,15 @@ const OnOffTemplate = memo(({ item = {}, doAction, sensor = {} }) => {
|
|
|
87
87
|
const triggerAction = useCallback(async () => {
|
|
88
88
|
const action_data = isOn ? action_off_data : action_on_data;
|
|
89
89
|
const config_value = isOn ? 0 : 1;
|
|
90
|
-
realTrigger(action_data, config_value);
|
|
90
|
+
realTrigger(action_data, config_value, false);
|
|
91
91
|
}, [isOn, action_off_data, action_on_data, realTrigger]);
|
|
92
92
|
|
|
93
93
|
const triggerOnAction = useCallback(async () => {
|
|
94
|
-
realTrigger(action_on_data, 1);
|
|
94
|
+
realTrigger(action_on_data, 1, true);
|
|
95
95
|
}, [action_on_data, realTrigger]);
|
|
96
96
|
|
|
97
97
|
const triggerOffAction = useCallback(async () => {
|
|
98
|
-
realTrigger(action_off_data, 0);
|
|
98
|
+
realTrigger(action_off_data, 0, true);
|
|
99
99
|
}, [action_off_data, realTrigger]);
|
|
100
100
|
|
|
101
101
|
useUnwatchLGDeviceConfigControl(sensor, [config]);
|
|
@@ -60,8 +60,8 @@ const TerminalBoxTemplate = ({ item, doAction, isWidgetOrder }) => {
|
|
|
60
60
|
);
|
|
61
61
|
const scrollViewRef = useRef();
|
|
62
62
|
const sendCommand = useCallback(() => {
|
|
63
|
-
doAction(action_data, JSON.stringify({ value: value }));
|
|
64
63
|
setValue('');
|
|
64
|
+
doAction(action_data, JSON.stringify({ value: value }));
|
|
65
65
|
}, [doAction, action_data, value]);
|
|
66
66
|
|
|
67
67
|
const onInputChange = (e) => {
|
|
@@ -85,7 +85,7 @@ const TerminalBoxTemplate = ({ item, doAction, isWidgetOrder }) => {
|
|
|
85
85
|
|
|
86
86
|
const scrollToBottom = () => {
|
|
87
87
|
setTimeout(() => {
|
|
88
|
-
scrollViewRef.current
|
|
88
|
+
scrollViewRef.current?.scrollToEnd({ animated: true });
|
|
89
89
|
}, 100);
|
|
90
90
|
};
|
|
91
91
|
|
|
@@ -165,6 +165,8 @@ const TerminalBoxTemplate = ({ item, doAction, isWidgetOrder }) => {
|
|
|
165
165
|
value={value}
|
|
166
166
|
onChange={onInputChange}
|
|
167
167
|
maxLength={255}
|
|
168
|
+
returnKeyType={'done'}
|
|
169
|
+
onSubmitEditing={sendCommand}
|
|
168
170
|
/>
|
|
169
171
|
<TouchableOpacity
|
|
170
172
|
style={styles.iconAndTextOption}
|
|
@@ -64,9 +64,12 @@ const TextBoxTemplate = ({ item, doAction, isWidgetOrder }) => {
|
|
|
64
64
|
transY={transY}
|
|
65
65
|
>
|
|
66
66
|
<_TextInput
|
|
67
|
+
autoFocus
|
|
67
68
|
wrapStyle={styles.wrapInputStyle}
|
|
68
69
|
value={value}
|
|
69
70
|
onChange={onInputChange}
|
|
71
|
+
returnKeyType={'done'}
|
|
72
|
+
onSubmitEditing={onDone}
|
|
70
73
|
/>
|
|
71
74
|
</AlertAction>
|
|
72
75
|
</View>
|
|
@@ -122,7 +122,7 @@ describe('Test OneBigButtonTemplate', () => {
|
|
|
122
122
|
await act(async () => {
|
|
123
123
|
await button.props.onPress();
|
|
124
124
|
});
|
|
125
|
-
expect(mockDoAction).toHaveBeenCalledWith(action_data, undefined);
|
|
125
|
+
expect(mockDoAction).toHaveBeenCalledWith(action_data, undefined, false);
|
|
126
126
|
};
|
|
127
127
|
|
|
128
128
|
it('action state on', async () => {
|
|
@@ -148,7 +148,7 @@ describe('Test OnOffTemplate', () => {
|
|
|
148
148
|
await act(async () => {
|
|
149
149
|
await template.props.triggerAction();
|
|
150
150
|
});
|
|
151
|
-
expect(mockDoAction).toHaveBeenCalledWith(actionOffData, undefined);
|
|
151
|
+
expect(mockDoAction).toHaveBeenCalledWith(actionOffData, undefined, false);
|
|
152
152
|
expect(watchMultiConfigs).toBeCalledTimes(0);
|
|
153
153
|
});
|
|
154
154
|
|
|
@@ -165,7 +165,7 @@ describe('Test OnOffTemplate', () => {
|
|
|
165
165
|
await act(async () => {
|
|
166
166
|
await template.props.triggerAction();
|
|
167
167
|
});
|
|
168
|
-
expect(mockDoAction).toHaveBeenCalledWith(actionOnData, undefined);
|
|
168
|
+
expect(mockDoAction).toHaveBeenCalledWith(actionOnData, undefined, false);
|
|
169
169
|
});
|
|
170
170
|
|
|
171
171
|
it('template OnOffSimpleActionTemplate doAction with is_on_value and is_managed_by_backend', async () => {
|
|
@@ -180,7 +180,7 @@ describe('Test OnOffTemplate', () => {
|
|
|
180
180
|
await act(async () => {
|
|
181
181
|
await template.props.triggerAction();
|
|
182
182
|
});
|
|
183
|
-
expect(mockDoAction).toHaveBeenCalledWith(actionOffData, undefined);
|
|
183
|
+
expect(mockDoAction).toHaveBeenCalledWith(actionOffData, undefined, false);
|
|
184
184
|
expect(watchMultiConfigs).toBeCalledTimes(0);
|
|
185
185
|
});
|
|
186
186
|
|
|
@@ -207,7 +207,7 @@ describe('Test OnOffTemplate', () => {
|
|
|
207
207
|
await act(async () => {
|
|
208
208
|
await template[0].props.triggerAction();
|
|
209
209
|
});
|
|
210
|
-
expect(mockDoAction).toHaveBeenCalledWith(actionData, undefined);
|
|
210
|
+
expect(mockDoAction).toHaveBeenCalledWith(actionData, undefined, false);
|
|
211
211
|
});
|
|
212
212
|
|
|
213
213
|
it('render with template OnOffSimpleActionTemplate with just action_data lg_thinq', async () => {
|
|
@@ -242,7 +242,7 @@ describe('Test OnOffTemplate', () => {
|
|
|
242
242
|
await act(async () => {
|
|
243
243
|
jest.runAllTimers();
|
|
244
244
|
});
|
|
245
|
-
expect(mockDoAction).toHaveBeenCalledWith(actionData, undefined);
|
|
245
|
+
expect(mockDoAction).toHaveBeenCalledWith(actionData, undefined, false);
|
|
246
246
|
});
|
|
247
247
|
|
|
248
248
|
it('render with template OnOffSimpleActionTemplate with action_data zigbee trigger off', async () => {
|
|
@@ -272,10 +272,14 @@ describe('Test OnOffTemplate', () => {
|
|
|
272
272
|
await act(async () => {
|
|
273
273
|
await template[0].props.triggerAction();
|
|
274
274
|
});
|
|
275
|
-
expect(mockDoAction).toHaveBeenCalledWith(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
275
|
+
expect(mockDoAction).toHaveBeenCalledWith(
|
|
276
|
+
actionOffData,
|
|
277
|
+
{
|
|
278
|
+
config_id: 5,
|
|
279
|
+
config_value: 0,
|
|
280
|
+
},
|
|
281
|
+
false
|
|
282
|
+
);
|
|
279
283
|
});
|
|
280
284
|
|
|
281
285
|
it('render with template OnOffSimpleActionTemplate with action_data zigbee trigger on', async () => {
|
|
@@ -305,10 +309,14 @@ describe('Test OnOffTemplate', () => {
|
|
|
305
309
|
await act(async () => {
|
|
306
310
|
await template[0].props.triggerAction();
|
|
307
311
|
});
|
|
308
|
-
expect(mockDoAction).toHaveBeenCalledWith(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
+
expect(mockDoAction).toHaveBeenCalledWith(
|
|
313
|
+
actionOnData,
|
|
314
|
+
{
|
|
315
|
+
config_id: 5,
|
|
316
|
+
config_value: 1,
|
|
317
|
+
},
|
|
318
|
+
false
|
|
319
|
+
);
|
|
312
320
|
});
|
|
313
321
|
|
|
314
322
|
it('render with template OnOffSimpleActionTemplate with zigbee device wrong action_data', async () => {
|
|
@@ -375,14 +375,16 @@ describe('Test ActionGroup', () => {
|
|
|
375
375
|
});
|
|
376
376
|
expect(mockDoAction).toHaveBeenCalledWith(
|
|
377
377
|
actionGroup.configuration.action_on_data,
|
|
378
|
-
undefined
|
|
378
|
+
undefined,
|
|
379
|
+
true
|
|
379
380
|
);
|
|
380
381
|
await act(async () => {
|
|
381
382
|
buttons[0].props.onPressOut();
|
|
382
383
|
});
|
|
383
384
|
expect(mockDoAction).toHaveBeenCalledWith(
|
|
384
385
|
actionGroup.configuration.action_off_data,
|
|
385
|
-
undefined
|
|
386
|
+
undefined,
|
|
387
|
+
true
|
|
386
388
|
);
|
|
387
389
|
});
|
|
388
390
|
|
|
@@ -31,6 +31,7 @@ const _TextInput = ({
|
|
|
31
31
|
onSelectionChange,
|
|
32
32
|
accessibilityLabelError,
|
|
33
33
|
onEndEditing,
|
|
34
|
+
returnKeyType = 'default',
|
|
34
35
|
}) => {
|
|
35
36
|
const errorStyle = !!errorText && styles.errorWrap;
|
|
36
37
|
return (
|
|
@@ -71,6 +72,7 @@ const _TextInput = ({
|
|
|
71
72
|
selection={selection}
|
|
72
73
|
onSelectionChange={onSelectionChange}
|
|
73
74
|
onEndEditing={onEndEditing}
|
|
75
|
+
returnKeyType={returnKeyType}
|
|
74
76
|
/>
|
|
75
77
|
{extraText && extraText}
|
|
76
78
|
{!!errorText && (
|
|
@@ -1,6 +1,37 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useMemo, useState } from 'react';
|
|
2
2
|
import WebView from 'react-native-webview';
|
|
3
3
|
|
|
4
|
+
const flattenText = (item, key) => {
|
|
5
|
+
let str = '';
|
|
6
|
+
if (item && typeof item === 'object' && item.length === undefined) {
|
|
7
|
+
str += flattenObject(item);
|
|
8
|
+
} else if (item && typeof item === 'object' && item.length !== undefined) {
|
|
9
|
+
str += '[';
|
|
10
|
+
item.forEach((k2) => {
|
|
11
|
+
str += `${flattenText(k2)}, `;
|
|
12
|
+
});
|
|
13
|
+
if (item.length > 0) {
|
|
14
|
+
str = str.slice(0, str.length - 2);
|
|
15
|
+
}
|
|
16
|
+
str += ']';
|
|
17
|
+
} else if (typeof item === 'string' && item.slice(0, 8) === 'function') {
|
|
18
|
+
str += `${item}`;
|
|
19
|
+
} else if (typeof item === 'string') {
|
|
20
|
+
// eslint-disable-next-line no-useless-escape
|
|
21
|
+
str += `\"${item.replace(/"/g, '\\"')}\"`;
|
|
22
|
+
} else {
|
|
23
|
+
str += `${item}`;
|
|
24
|
+
}
|
|
25
|
+
return str;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const flattenObject = (obj, str = '{') => {
|
|
29
|
+
Object.keys(obj).forEach(function (key) {
|
|
30
|
+
str += `${key}: ${flattenText(obj[key])}, `;
|
|
31
|
+
});
|
|
32
|
+
return `${str.slice(0, str.length - 2)}}`;
|
|
33
|
+
};
|
|
34
|
+
|
|
4
35
|
const Highcharts = (props) => {
|
|
5
36
|
const [init] = useState(`<html>
|
|
6
37
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0" />
|
|
@@ -36,11 +67,12 @@ const Highcharts = (props) => {
|
|
|
36
67
|
}
|
|
37
68
|
<script src="https://code.highcharts.com/modules/exporting.js"></script>
|
|
38
69
|
<script>
|
|
70
|
+
window.chartObj = null;
|
|
39
71
|
$(function () {
|
|
40
72
|
Highcharts.setOptions(${JSON.stringify(
|
|
41
73
|
props.options
|
|
42
74
|
)});
|
|
43
|
-
Highcharts.${
|
|
75
|
+
window.chartObj = Highcharts.${
|
|
44
76
|
props.stock ? 'stockChart' : 'chart'
|
|
45
77
|
}('container', `);
|
|
46
78
|
const [end] = useState(` );
|
|
@@ -53,46 +85,13 @@ const Highcharts = (props) => {
|
|
|
53
85
|
</body>
|
|
54
86
|
</html>`);
|
|
55
87
|
|
|
56
|
-
const
|
|
57
|
-
let
|
|
58
|
-
|
|
59
|
-
str += flattenObject(item);
|
|
60
|
-
} else if (item && typeof item === 'object' && item.length !== undefined) {
|
|
61
|
-
str += '[';
|
|
62
|
-
item.forEach((k2) => {
|
|
63
|
-
str += `${flattenText(k2)}, `;
|
|
64
|
-
});
|
|
65
|
-
if (item.length > 0) {
|
|
66
|
-
str = str.slice(0, str.length - 2);
|
|
67
|
-
}
|
|
68
|
-
str += ']';
|
|
69
|
-
} else if (typeof item === 'string' && item.slice(0, 8) === 'function') {
|
|
70
|
-
str += `${item}`;
|
|
71
|
-
} else if (typeof item === 'string') {
|
|
72
|
-
// eslint-disable-next-line no-useless-escape
|
|
73
|
-
str += `\"${item.replace(/"/g, '\\"')}\"`;
|
|
74
|
-
} else {
|
|
75
|
-
str += `${item}`;
|
|
76
|
-
}
|
|
77
|
-
return str;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const flattenObject = (obj, str = '{') => {
|
|
81
|
-
Object.keys(obj).forEach(function (key) {
|
|
82
|
-
str += `${key}: ${flattenText(obj[key])}, `;
|
|
88
|
+
const concatHTML = useMemo(() => {
|
|
89
|
+
let config = JSON.stringify(props.options, (key, value) => {
|
|
90
|
+
return typeof value === 'function' ? value.toString() : value;
|
|
83
91
|
});
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
let config = JSON.stringify(props.options, (key, value) => {
|
|
88
|
-
return typeof value === 'function' ? value.toString() : value;
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
config = JSON.parse(config);
|
|
92
|
-
const concatHTML = `${init}${flattenObject(config)}${end}`.replace(
|
|
93
|
-
': }',
|
|
94
|
-
': {}'
|
|
95
|
-
);
|
|
92
|
+
config = JSON.parse(config);
|
|
93
|
+
return `${init}${flattenObject(config)}${end}`.replace(': }', ': {}');
|
|
94
|
+
}, [end, init, props?.options]);
|
|
96
95
|
|
|
97
96
|
return (
|
|
98
97
|
<WebView
|
|
@@ -103,6 +102,7 @@ const Highcharts = (props) => {
|
|
|
103
102
|
scalesPageToFit={true}
|
|
104
103
|
scrollEnabled={false}
|
|
105
104
|
automaticallyAdjustContentInsets={true}
|
|
105
|
+
ref={props?.setRef}
|
|
106
106
|
{...props}
|
|
107
107
|
/>
|
|
108
108
|
);
|
|
@@ -23,7 +23,7 @@ export const useFetchConfigHistory = (
|
|
|
23
23
|
endpoint = API.CONFIG.DISPLAY_HISTORY_V3()
|
|
24
24
|
) => {
|
|
25
25
|
const fetchDataDisplayHistory = useCallback(
|
|
26
|
-
async (startDate, endDate) => {
|
|
26
|
+
async (startDate, endDate, setProcessing = () => {}) => {
|
|
27
27
|
if (!configs.length || !startDate || !endDate) {
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
@@ -33,6 +33,7 @@ export const useFetchConfigHistory = (
|
|
|
33
33
|
if (!validConfigs.length) {
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
+
setProcessing(true);
|
|
36
37
|
validConfigs.forEach((item) => {
|
|
37
38
|
params.append('configs', item.id);
|
|
38
39
|
});
|
|
@@ -48,6 +49,7 @@ export const useFetchConfigHistory = (
|
|
|
48
49
|
params,
|
|
49
50
|
});
|
|
50
51
|
await updateConfigChart(success, data, configs, setChartData);
|
|
52
|
+
setProcessing(false);
|
|
51
53
|
},
|
|
52
54
|
[configs, endpoint, setChartData]
|
|
53
55
|
);
|
package/src/configs/Constants.js
CHANGED
|
@@ -5,15 +5,22 @@ import DateTimeRangeChange from '../../../commons/DateTimeRangeChange';
|
|
|
5
5
|
import moment from 'moment';
|
|
6
6
|
|
|
7
7
|
const ChartWrapper = memo(
|
|
8
|
-
({
|
|
8
|
+
({
|
|
9
|
+
children,
|
|
10
|
+
onChangeDate,
|
|
11
|
+
showTime,
|
|
12
|
+
isWidgetOrder,
|
|
13
|
+
periodDefault,
|
|
14
|
+
setProcessing,
|
|
15
|
+
}) => {
|
|
9
16
|
const [value, setValue] = useState([
|
|
10
|
-
moment().subtract(
|
|
17
|
+
moment().subtract(periodDefault, 'hour'),
|
|
11
18
|
moment(),
|
|
12
19
|
]);
|
|
13
20
|
|
|
14
21
|
useEffect(() => {
|
|
15
|
-
onChangeDate(value[0], value[1]);
|
|
16
|
-
}, [onChangeDate, value]);
|
|
22
|
+
onChangeDate(value[0], value[1], setProcessing);
|
|
23
|
+
}, [onChangeDate, setProcessing, value]);
|
|
17
24
|
|
|
18
25
|
const selectStart = (date) => {
|
|
19
26
|
setValue((state) => [date, state[1]]);
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { StyleSheet, View } from 'react-native';
|
|
3
|
+
import { Colors } from '../../../configs';
|
|
4
|
+
import Text from '../../../commons/Text';
|
|
5
|
+
import Highcharts from '../../../commons/Highcharts';
|
|
6
|
+
import { useConfigGlobalState } from '../../../iot/states';
|
|
7
|
+
|
|
8
|
+
const chartOptions = {
|
|
9
|
+
chart: {
|
|
10
|
+
type: 'pie',
|
|
11
|
+
height: '80%',
|
|
12
|
+
},
|
|
13
|
+
credits: {
|
|
14
|
+
enabled: false,
|
|
15
|
+
},
|
|
16
|
+
exporting: {
|
|
17
|
+
enabled: false,
|
|
18
|
+
},
|
|
19
|
+
title: {
|
|
20
|
+
text: '',
|
|
21
|
+
},
|
|
22
|
+
legend: {
|
|
23
|
+
enabled: true,
|
|
24
|
+
},
|
|
25
|
+
plotOptions: {
|
|
26
|
+
pie: {
|
|
27
|
+
showInLegend: true,
|
|
28
|
+
},
|
|
29
|
+
series: {
|
|
30
|
+
type: 'pie',
|
|
31
|
+
size: '100%',
|
|
32
|
+
innerSize: '50%',
|
|
33
|
+
dataLabels: [
|
|
34
|
+
{
|
|
35
|
+
enabled: true,
|
|
36
|
+
distance: 5,
|
|
37
|
+
connectorWidth: 0,
|
|
38
|
+
style: {
|
|
39
|
+
fontWeight: 'normal',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
enabled: true,
|
|
44
|
+
distance: -30,
|
|
45
|
+
format: '{point.y}',
|
|
46
|
+
style: {
|
|
47
|
+
fontWeight: 'normal',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
tooltip: {
|
|
54
|
+
format: '<b>{point.name}: {point.y}</b>',
|
|
55
|
+
shared: true,
|
|
56
|
+
},
|
|
57
|
+
series: [
|
|
58
|
+
{
|
|
59
|
+
type: 'pie',
|
|
60
|
+
data: [],
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const DonutCharts = ({ item, isWidgetOrder }) => {
|
|
66
|
+
const { configuration, label } = item;
|
|
67
|
+
const { configs = [] } = configuration;
|
|
68
|
+
const [configValues] = useConfigGlobalState('configValues');
|
|
69
|
+
const [highchartsWebviewRef, setHighchartsWebviewRef] = useState(null);
|
|
70
|
+
|
|
71
|
+
const getConfigValue = useCallback(
|
|
72
|
+
(configId) => {
|
|
73
|
+
const value = configValues[configId]?.value;
|
|
74
|
+
return typeof value === 'number' ? value : 0;
|
|
75
|
+
},
|
|
76
|
+
[configValues]
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (!highchartsWebviewRef) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const data = configs.map((config) => ({
|
|
84
|
+
y: getConfigValue(config.id),
|
|
85
|
+
name: config.name,
|
|
86
|
+
color: config.color,
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
highchartsWebviewRef.injectJavaScript(
|
|
90
|
+
`window.chartObj.series[0].setData(${JSON.stringify(data)})`
|
|
91
|
+
);
|
|
92
|
+
}, [configs, getConfigValue, highchartsWebviewRef]);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<View>
|
|
96
|
+
<View style={styles.titleHistory}>
|
|
97
|
+
<Text type="H3" semibold color={Colors.Gray9}>
|
|
98
|
+
{label}
|
|
99
|
+
</Text>
|
|
100
|
+
</View>
|
|
101
|
+
|
|
102
|
+
<Highcharts
|
|
103
|
+
setRef={setHighchartsWebviewRef}
|
|
104
|
+
options={chartOptions}
|
|
105
|
+
styles={styles.chartStyle}
|
|
106
|
+
webviewStyles={[
|
|
107
|
+
styles.webviewStyle,
|
|
108
|
+
isWidgetOrder && styles.widgetOrderWebview,
|
|
109
|
+
]}
|
|
110
|
+
/>
|
|
111
|
+
</View>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const styles = StyleSheet.create({
|
|
116
|
+
chartStyle: {
|
|
117
|
+
backgroundColor: Colors.White,
|
|
118
|
+
flex: 1,
|
|
119
|
+
},
|
|
120
|
+
titleHistory: {
|
|
121
|
+
flexDirection: 'row',
|
|
122
|
+
justifyContent: 'space-between',
|
|
123
|
+
paddingHorizontal: 16,
|
|
124
|
+
},
|
|
125
|
+
webviewStyle: {
|
|
126
|
+
flex: 1,
|
|
127
|
+
minHeight: 200,
|
|
128
|
+
height: 300,
|
|
129
|
+
},
|
|
130
|
+
widgetOrderWebview: {
|
|
131
|
+
minHeight: 200,
|
|
132
|
+
minWidth: 200,
|
|
133
|
+
width: 300,
|
|
134
|
+
height: 250,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
export default DonutCharts;
|
|
@@ -24,6 +24,8 @@ import { DetailHistoryChart } from './DetailHistoryChart';
|
|
|
24
24
|
import VisualChart from './VisualChart';
|
|
25
25
|
import { standardizeCameraScreenSize } from '../../../utils/Utils';
|
|
26
26
|
import { Device } from '../../../configs';
|
|
27
|
+
import DonutCharts from './DonutChart';
|
|
28
|
+
|
|
27
29
|
const { standardizeWidth, standardizeHeight } = standardizeCameraScreenSize(
|
|
28
30
|
Device.screenWidth - 32
|
|
29
31
|
);
|
|
@@ -58,11 +60,11 @@ export const SensorDisplayItem = ({
|
|
|
58
60
|
};
|
|
59
61
|
|
|
60
62
|
const getData = useCallback(() => {
|
|
61
|
-
if (!
|
|
63
|
+
if (!configuration) {
|
|
62
64
|
return;
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
const data = (
|
|
67
|
+
const data = (configuration.configs || []).map((config) => {
|
|
66
68
|
const configValue = configValues[config.id]?.value;
|
|
67
69
|
|
|
68
70
|
const configEvaluate = evaluate[config.id] || {};
|
|
@@ -86,20 +88,14 @@ export const SensorDisplayItem = ({
|
|
|
86
88
|
});
|
|
87
89
|
|
|
88
90
|
return data.filter(Boolean);
|
|
89
|
-
}, [
|
|
90
|
-
configValues,
|
|
91
|
-
evaluate,
|
|
92
|
-
evaluateValue,
|
|
93
|
-
item.configuration,
|
|
94
|
-
value_evaluation,
|
|
95
|
-
]);
|
|
91
|
+
}, [configValues, evaluate, evaluateValue, configuration, value_evaluation]);
|
|
96
92
|
|
|
97
93
|
const getDataCircleMini = useCallback(() => {
|
|
98
|
-
if (!
|
|
94
|
+
if (!configuration?.config?.id) {
|
|
99
95
|
return;
|
|
100
96
|
}
|
|
101
97
|
|
|
102
|
-
const config =
|
|
98
|
+
const config = configuration.config;
|
|
103
99
|
const configValue = configValues[config.id]?.value;
|
|
104
100
|
const configEvaluate = evaluate[config.id] || {};
|
|
105
101
|
|
|
@@ -114,17 +110,11 @@ export const SensorDisplayItem = ({
|
|
|
114
110
|
};
|
|
115
111
|
|
|
116
112
|
return [{ ...config, ...value }].filter(Boolean);
|
|
117
|
-
}, [
|
|
118
|
-
configValues,
|
|
119
|
-
evaluate,
|
|
120
|
-
evaluateValue,
|
|
121
|
-
item.configuration,
|
|
122
|
-
value_evaluation,
|
|
123
|
-
]);
|
|
113
|
+
}, [configValues, evaluate, evaluateValue, configuration, value_evaluation]);
|
|
124
114
|
|
|
125
115
|
const doAction = useCallback(
|
|
126
|
-
async (action, data) => {
|
|
127
|
-
if (processing) {
|
|
116
|
+
async (action, data, interrupted = false) => {
|
|
117
|
+
if (processing && !interrupted) {
|
|
128
118
|
return;
|
|
129
119
|
}
|
|
130
120
|
setProcessing(true);
|
|
@@ -187,7 +177,7 @@ export const SensorDisplayItem = ({
|
|
|
187
177
|
/>
|
|
188
178
|
);
|
|
189
179
|
case 'history':
|
|
190
|
-
if (
|
|
180
|
+
if (configuration?.config === 'power_consumption') {
|
|
191
181
|
return <DetailHistoryChart item={item} sensor={sensor} />;
|
|
192
182
|
}
|
|
193
183
|
return (
|
|
@@ -254,6 +244,8 @@ export const SensorDisplayItem = ({
|
|
|
254
244
|
offsetTitle={offsetTitle}
|
|
255
245
|
/>
|
|
256
246
|
);
|
|
247
|
+
case 'donut_chart_race':
|
|
248
|
+
return <DonutCharts item={item} isWidgetOrder={isWidgetOrder} />;
|
|
257
249
|
default:
|
|
258
250
|
return <ListQualityIndicator data={getData(item)} />;
|
|
259
251
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react';
|
|
2
8
|
import moment from 'moment/moment';
|
|
3
9
|
import _ from 'lodash';
|
|
4
10
|
|
|
@@ -8,9 +14,11 @@ import { StyleSheet, View } from 'react-native';
|
|
|
8
14
|
import { Colors } from '../../../configs';
|
|
9
15
|
import ChartAggregationOption from '../../../commons/ChartAggregationOption';
|
|
10
16
|
import Text from '../../../commons/Text';
|
|
11
|
-
import { CHART_GROUP_TYPE } from '../../../configs/Constants';
|
|
17
|
+
import { CHART_GROUP_TYPE, CHART_TIME } from '../../../configs/Constants';
|
|
12
18
|
import { getDateRangeOfWeek } from '../../../utils/Utils';
|
|
13
19
|
import Highcharts from '../../../commons/Highcharts';
|
|
20
|
+
import { FullLoading } from '../../../commons';
|
|
21
|
+
import { colorOpacity } from '../../../utils/Converter/color';
|
|
14
22
|
|
|
15
23
|
const groupByTime = (format) => (x) => x[0].format(format);
|
|
16
24
|
|
|
@@ -129,6 +137,9 @@ const styles = StyleSheet.create({
|
|
|
129
137
|
width: 300,
|
|
130
138
|
height: 250,
|
|
131
139
|
},
|
|
140
|
+
backgroundLoading: {
|
|
141
|
+
backgroundColor: colorOpacity(Colors.White, 0),
|
|
142
|
+
},
|
|
132
143
|
});
|
|
133
144
|
|
|
134
145
|
const chartOptions = {
|
|
@@ -174,13 +185,16 @@ const VisualChart = ({ item, isDemo = false, isWidgetOrder }) => {
|
|
|
174
185
|
const {
|
|
175
186
|
configs = [],
|
|
176
187
|
value_type = 'raw',
|
|
177
|
-
aggregation_period,
|
|
178
|
-
date_format,
|
|
179
|
-
show_time,
|
|
188
|
+
aggregation_period = 'day',
|
|
189
|
+
date_format = 'YYYY-MM-DD HH:mm',
|
|
190
|
+
show_time = true,
|
|
191
|
+
type,
|
|
180
192
|
} = configuration;
|
|
193
|
+
|
|
181
194
|
const canChooseGroup = value_type !== 'raw';
|
|
182
195
|
const [chartData, setChartData] = useState(configs);
|
|
183
196
|
const [options, setOption] = useState(chartOptions);
|
|
197
|
+
const [processing, setProcessing] = useState(false);
|
|
184
198
|
const [groupBy, setGroupBy] = useState(
|
|
185
199
|
canChooseGroup ? aggregation_period : null
|
|
186
200
|
);
|
|
@@ -231,7 +245,7 @@ const VisualChart = ({ item, isDemo = false, isWidgetOrder }) => {
|
|
|
231
245
|
chart: { ...prev.chart },
|
|
232
246
|
plotOptions: { ...prev.plotOptions },
|
|
233
247
|
};
|
|
234
|
-
switch (
|
|
248
|
+
switch (type) {
|
|
235
249
|
case CHART_TYPE_ENUM.bar_chart:
|
|
236
250
|
newOption.chart.type = 'column';
|
|
237
251
|
newOption.plotOptions.column = {
|
|
@@ -281,13 +295,20 @@ const VisualChart = ({ item, isDemo = false, isWidgetOrder }) => {
|
|
|
281
295
|
});
|
|
282
296
|
|
|
283
297
|
chartRef.current?.chart?.redraw();
|
|
284
|
-
}, [
|
|
298
|
+
}, [type]);
|
|
285
299
|
|
|
286
300
|
useEffect(() => {
|
|
287
301
|
if (!isDemo) {
|
|
288
302
|
updateChart();
|
|
289
303
|
}
|
|
290
|
-
}, [updateChart, chartData,
|
|
304
|
+
}, [updateChart, chartData, configs.length, isDemo]);
|
|
305
|
+
|
|
306
|
+
const getPeriodDefault = useMemo(() => {
|
|
307
|
+
if (value_type === 'raw') {
|
|
308
|
+
return CHART_TIME[aggregation_period];
|
|
309
|
+
}
|
|
310
|
+
return CHART_TIME.week;
|
|
311
|
+
}, [aggregation_period, value_type]);
|
|
291
312
|
|
|
292
313
|
return (
|
|
293
314
|
<View style={styles.container}>
|
|
@@ -305,11 +326,14 @@ const VisualChart = ({ item, isDemo = false, isWidgetOrder }) => {
|
|
|
305
326
|
/>
|
|
306
327
|
)}
|
|
307
328
|
</View>
|
|
329
|
+
{processing && <FullLoading wrapStyle={styles.backgroundLoading} />}
|
|
308
330
|
<ChartWrapper
|
|
309
331
|
onChangeDate={fetchDataDisplayHistory}
|
|
310
332
|
dateFormat={date_format}
|
|
311
333
|
showTime={show_time}
|
|
312
334
|
isWidgetOrder={isWidgetOrder}
|
|
335
|
+
periodDefault={getPeriodDefault}
|
|
336
|
+
setProcessing={setProcessing}
|
|
313
337
|
>
|
|
314
338
|
<Highcharts
|
|
315
339
|
options={options}
|
|
@@ -12,19 +12,21 @@ const SegmentedRoundChart = memo(({ data }) => {
|
|
|
12
12
|
const { id, color, title } = data || {};
|
|
13
13
|
const [configValues] = useConfigGlobalState('configValues');
|
|
14
14
|
|
|
15
|
+
const getValue = configValues[id]?.value ?? '--';
|
|
16
|
+
|
|
15
17
|
const renderAir = useCallback(() => {
|
|
16
18
|
return (
|
|
17
19
|
<SegmentedRoundDisplay
|
|
18
20
|
filledArcColor={color}
|
|
19
|
-
value={
|
|
20
|
-
valueText={
|
|
21
|
+
value={getValue}
|
|
22
|
+
valueText={getValue}
|
|
21
23
|
totalValue={500}
|
|
22
24
|
title={title}
|
|
23
25
|
style={styles.segment}
|
|
24
26
|
textHeader={t('text_air_quality_index')}
|
|
25
27
|
/>
|
|
26
28
|
);
|
|
27
|
-
}, [color,
|
|
29
|
+
}, [color, getValue, t, title]);
|
|
28
30
|
|
|
29
31
|
return renderAir();
|
|
30
32
|
});
|
|
@@ -13,7 +13,7 @@ const SegmentedRoundChart = memo(({ data }) => {
|
|
|
13
13
|
const [configValues] = useConfigGlobalState('configValues');
|
|
14
14
|
|
|
15
15
|
const renderAir = useCallback(() => {
|
|
16
|
-
const getValue = configValues
|
|
16
|
+
const getValue = configValues[id]?.value ?? '--';
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
19
|
<SegmentedRoundDisplay
|
|
@@ -116,36 +116,6 @@ const WaterQuality = memo(({ summaryDetail }) => {
|
|
|
116
116
|
{datas.map((item) => (
|
|
117
117
|
<Item {...item} key={item.id} />
|
|
118
118
|
))}
|
|
119
|
-
{/* <Item
|
|
120
|
-
title={t('Turbidity')}
|
|
121
|
-
value={summaryDetail.tur_value}
|
|
122
|
-
color={summaryDetail.tur_color}
|
|
123
|
-
des={summaryDetail.tur_status}
|
|
124
|
-
svgMain={''}
|
|
125
|
-
waterType="turbidity"
|
|
126
|
-
/>
|
|
127
|
-
<Item
|
|
128
|
-
title={t('pH')}
|
|
129
|
-
value={summaryDetail.ph_value}
|
|
130
|
-
color={summaryDetail.ph_color}
|
|
131
|
-
des={summaryDetail.ph_status}
|
|
132
|
-
svgMain={''}
|
|
133
|
-
waterType="ph"
|
|
134
|
-
/>
|
|
135
|
-
<Item
|
|
136
|
-
title={t('Chlorine residual')}
|
|
137
|
-
value={summaryDetail.clo_value}
|
|
138
|
-
color={summaryDetail.clo_color}
|
|
139
|
-
des={summaryDetail.clo_status}
|
|
140
|
-
svgMain={''}
|
|
141
|
-
waterType="clo"
|
|
142
|
-
/>
|
|
143
|
-
<Item
|
|
144
|
-
title={t('Water temperature')}
|
|
145
|
-
value={summaryDetail.temp_value}
|
|
146
|
-
des={''}
|
|
147
|
-
svgMain={''}
|
|
148
|
-
/> */}
|
|
149
119
|
</View>
|
|
150
120
|
</Section>
|
|
151
121
|
{showBoxHistory && (
|