diff --git a/front/src/components/boxs/chart/ApexChartComponent.jsx b/front/src/components/boxs/chart/ApexChartComponent.jsx index f4d96144ee..9a74043f19 100644 --- a/front/src/components/boxs/chart/ApexChartComponent.jsx +++ b/front/src/components/boxs/chart/ApexChartComponent.jsx @@ -141,16 +141,105 @@ class ApexChartComponent extends Component { } else if (this.props.size === 'big' && !this.props.display_axes) { height = 80; } else { + console.log('height this.props', this.props); height = 200 + this.props.additionalHeight; } + console.log('height', height); + let seriesAnnotationsYaxis = []; + let seriesPoints = []; + console.log('this.props', this.props); + if (this.props.seriesAnnotationsYaxis) { + // console.log('seriesAnnotationsYaxis', this.props.seriesAnnotationsYaxis); + seriesAnnotationsYaxis = this.props.seriesAnnotationsYaxis.flatMap(annotation => { + const annotationY = { + y: annotation.y, // Valeur de la ligne min + borderColor: annotation.color, // Couleur de la ligne + // strokeDashArray: 2, + // fillColor: annotation.color, + + fillColor: annotation.color, + marker: { + size: 4, + fillColor: "#fff", + strokeWidth: 2, + strokeColor: "#333", + }, + // opacity: 0.5, + width: '200%', + }; + if (annotation.y2) { + annotationY.y2 = annotation.y2; + } + if (annotation.more_or_less === 'moreOrLess') { + const { y2, ...annotationYWithoutY2 } = annotationY; + const additionalAnnotationY = { + // Copy the existing properties except y2 + ...annotationYWithoutY2, + y: annotation.value, // Set 'y' to 'annotation.value' + + label: { + borderColor: annotation.color, + style: { + color: '#fff', + background: annotation.color + }, + text: annotation.name + } + }; + return [annotationY, additionalAnnotationY] + } else { + annotationY.label = { + borderColor: annotation.color, + style: { + color: '#fff', + background: annotation.color + }, + text: annotation.name + }; + } + return annotationY; + }); + } + if (this.props.seriesPoints) { + console.log('seriesAnnotationsXaxis', this.props.seriesAnnotationsXaxis); + seriesPoints = this.props.seriesPoints.map(point => { + console.log('point', point); + const data = point.data.map(p => { + return { + y: p.y, + // y2: 24, + x: new Date(p.x).getTime(), + borderColor: '#FF4560', + label: { + text: point.name + }, + // marker: { + // size: 4, + // fillColor: '#fff', + // strokeWidth: 2, + // strokeColor: "#333", // A visible stroke color + // shape: "circle", + // }, + // position: 'front' + }; + }); + console.log('data', data); + return data; + }); + seriesPoints = mergeArray(...seriesPoints); + } + console.log('getLineChartOptions seriesPoints', seriesPoints); const options = getApexChartLineOptions({ height, colors: mergeArray(this.props.colors, DEFAULT_COLORS), displayAxes: this.props.display_axes, series: this.props.series, locales: [fr, en, de], - defaultLocale: this.props.user.language + defaultLocale: this.props.user.language, + seriesPoints, + seriesAnnotationsYaxis }); + console.log('getLineChartOptions options', options); this.addDateFormatter(options); return options; }; @@ -223,6 +312,8 @@ class ApexChartComponent extends Component { } componentDidUpdate(nextProps) { const seriesDifferent = nextProps.series !== this.props.series; + const seriesPointsDifferent = nextProps.seriesPoints !== this.props.seriesPoints; + const seriesAnnotationsYaxisDifferent = nextProps.seriesAnnotationsYaxis !== this.props.seriesAnnotationsYaxis; const chartTypeDifferent = nextProps.chart_type !== this.props.chart_type; const displayAxesDifferent = nextProps.display_axes !== this.props.display_axes; const intervalDifferent = nextProps.interval !== this.props.interval; @@ -234,7 +325,9 @@ class ApexChartComponent extends Component { displayAxesDifferent || intervalDifferent || sizeDifferent || - additionalHeightDifferent + additionalHeightDifferent || + seriesPointsDifferent || + seriesAnnotationsYaxisDifferent ) { this.displayChart(); } diff --git a/front/src/components/boxs/chart/ApexChartLineOptions.js b/front/src/components/boxs/chart/ApexChartLineOptions.js index 24a9f2365d..0e59186c70 100644 --- a/front/src/components/boxs/chart/ApexChartLineOptions.js +++ b/front/src/components/boxs/chart/ApexChartLineOptions.js @@ -1,4 +1,4 @@ -const getApexChartLineOptions = ({ height, displayAxes, series, colors, locales, defaultLocale }) => { +const getApexChartLineOptions = ({ height, displayAxes, series, colors, locales, defaultLocale, seriesPoints, seriesAnnotationsYaxis }) => { const options = { chart: { locales, @@ -64,6 +64,72 @@ const getApexChartLineOptions = ({ height, displayAxes, series, colors, locales, legend: { show: displayAxes, position: 'bottom' + }, + annotations: { + yaxis: seriesAnnotationsYaxis, + // points: seriesPoints, + + // points: [ + // // { + // // y: 5, // Valeur de la ligne min + // // borderColor: '#FF0000', // Couleur de la ligne + // // label: { + // // borderColor: '#FF0000', + // // style: { + // // color: '#fff', + // // background: '#FF0000' + // // }, + // // text: 'Min Value' + // // } + // // }, + // // { + // // y: 20, // Valeur de la ligne max + // // borderColor: '#00FF00', // Couleur de la ligne + // // label: { + // // borderColor: '#00FF00', + // // style: { + // // color: '#fff', + // // background: '#00FF00' + // // }, + // // text: 'Max Value' + // // } + // // }, + // // { + // // y: 22, + // // y2: 24, + // // borderColor: '#00E396', + // // label: { + // // text: 'Consigne 8:00' + // // } + // // }, + // // { + // // y: 17, + // // x: 3, // Position sur l'axe X (11:00) + // // borderColor: '#FEB019', + // // label: { + // // text: 'Consigne 11:00' + // // } + // // }, + // { + // y: 20, + // y2: 24, + // x: 1725638947166, // Position sur l'axe X (15:00) + // borderColor: '#FF4560', + // label: { + // text: 'Consigne 15:00' + // } + // }, + // { + // y: 20, + // y2: 24, + // x: 1725649979366, // Position sur l'axe X (15:00) + // borderColor: '#FF4560', + // label: { + // text: 'Consigne 15:00' + // } + // } + + // ] } }; return options; diff --git a/front/src/components/boxs/chart/Chart.jsx b/front/src/components/boxs/chart/Chart.jsx index 83502973f1..6ddc941b91 100644 --- a/front/src/components/boxs/chart/Chart.jsx +++ b/front/src/components/boxs/chart/Chart.jsx @@ -116,6 +116,8 @@ class Chartbox extends Component { }; getData = async () => { let deviceFeatures = this.props.box.device_features; + let deviceFeaturesTreshold = this.props.box.device_features_treshold; + let manualFeaturesTreshold = this.props.box.manual_features_treshold_details; let deviceFeatureNames = this.props.box.device_feature_names; let nbFeaturesDisplayed = deviceFeatures.length; @@ -208,7 +210,6 @@ class Chartbox extends Component { } const newState = { series, - loading: false, initialized: true, emptySeries, nbFeaturesDisplayed @@ -260,6 +261,63 @@ class Chartbox extends Component { } catch (e) { console.error(e); } + console.log('deviceFeaturesTreshold', deviceFeaturesTreshold); + if (!deviceFeaturesTreshold || deviceFeaturesTreshold.length === 0) { + await this.setState({ loading: false }); + return; + } + try { + const data = await this.props.httpClient.get(`/api/v1/device_feature/aggregated_states`, { + interval: this.state.interval, + max_states: 10, + device_features: deviceFeaturesTreshold.join(',') + }); + + const seriesPoints = data.map((oneFeature, index) => { + const unit = this.props.box.units[index]; + const deviceFeatureUnitShort = this.props.intl.dictionary.deviceFeatureUnitShort[unit]; + const { values, deviceFeature } = oneFeature; + const deviceName = deviceFeature.name; + const name = `${deviceName} (${deviceFeatureUnitShort})`; + return { + name, + data: values.map(value => { + return { + x: value.created_at, + y: value.value + }; + }) + }; + }); + await this.setState({ seriesPoints }); + } catch (e) { + console.error(e); + } + + if (manualFeaturesTreshold.length === 0) { + await this.setState({ loading: false }); + return; + } + + if (manualFeaturesTreshold.length > 0) { + const seriesAnnotationsYaxis = manualFeaturesTreshold.map((oneFeature, index) => { + const unit = oneFeature.unit; + const deviceFeatureUnitShort = this.props.intl.dictionary.deviceFeatureUnitShort[unit]; + const deviceName = oneFeature.name; + const value = oneFeature.more_or_less && oneFeature.more_or_less !== 'without' ? + `${oneFeature.min}/${oneFeature.max}` : `${oneFeature.value}`; + const name = `${deviceName} ${value}${deviceFeatureUnitShort}`; + return { + name, + value: oneFeature.value, + y: oneFeature.min ? oneFeature.min : oneFeature.value, + y2: oneFeature.max ? oneFeature.max : oneFeature.value_more_or_less, + color: oneFeature.color, + more_or_less: oneFeature.more_or_less + }; + }); + await this.setState({ seriesAnnotationsYaxis }); + } }; updateDeviceStateWebsocket = payload => { if ( @@ -318,6 +376,8 @@ class Chartbox extends Component { initialized, loading, series, + seriesPoints, + seriesAnnotationsYaxis, dropdown, variation, variationDownIsPositive, @@ -502,6 +562,8 @@ class Chartbox extends Component {
{ + const { manualThresholdDetails } = this.state; + manualThresholdDetails.push({ name: '', value: '', unit: '', color: '', more_or_less: 'without' }); + const index = manualThresholdDetails.length - 1; + this.setState({ manualThresholdDetails }); + this.openEditForm(index); + }; + deleteManualThreshold = index => { + const { manualThresholdDetails } = this.state; + manualThresholdDetails.splice(index, 1); + this.setState({ manualThresholdDetails }); + this.props.updateBoxConfig(this.props.x, this.props.y, { + manual_features_treshold_details: manualThresholdDetails + }); + }; + + updateManualThresholdDetail = (index, field, value) => { + const { manualThresholdDetails } = this.state; + const detail = manualThresholdDetails[index]; + const isNumber = !isNaN(value); + detail[field] = isNumber ? Number(value) : value; + if (field === 'value' || field === 'value_more_or_less' || field === 'more_or_less') { + + if (detail.more_or_less === 'moreOrLess') { + detail.min = detail.value - detail.value_more_or_less; + detail.max = detail.value + detail.value_more_or_less; + } + if (detail.more_or_less === 'more') { + detail.min = detail.value; + detail.max = detail.value + detail.value_more_or_less; + } + if (detail.more_or_less === 'less') { + detail.min = detail.value - detail.value_more_or_less; + detail.max = detail.value; + } + if (detail.more_or_less === 'manual') { + detail.min = detail.value; + detail.max = detail.value_more_or_less; + } + } + if (field === 'value' && detail.more_or_less === 'manual') { + detail.min = value; + } + + this.props.updateBoxConfig(this.props.x, this.props.y, { + manual_features_treshold_details: manualThresholdDetails + }); + this.setState({ manualThresholdDetails }); + }; + + updateDeviceFeaturesTreshold = newSelectedTresholdOptions => { + if (newSelectedTresholdOptions && newSelectedTresholdOptions.length > 0) { + const deviceFeaturesSelectors = newSelectedTresholdOptions.map( + selectedDeviceFeaturesOption => selectedDeviceFeaturesOption.value + ); + this.props.updateBoxConfig(this.props.x, this.props.y, { + device_features_treshold: deviceFeaturesSelectors, + }); + } else { + this.props.updateBoxConfig(this.props.x, this.props.y, { + device_features_treshold: [], + }); + } + this.setState({ selectedTresholdOptions: newSelectedTresholdOptions }); + }; + + // updateDeviceFeatures = selectedDeviceFeaturesOptions => { addDeviceFeature = async selectedDeviceFeatureOption => { const newSelectedDeviceFeaturesOptions = [...this.state.selectedDeviceFeaturesOptions, selectedDeviceFeatureOption]; await this.setState({ selectedDeviceFeaturesOptions: newSelectedDeviceFeaturesOptions }); @@ -221,14 +291,21 @@ class EditChart extends Component { getSelectedDeviceFeaturesAndOptions = (devices, chartType = this.state.chart_type) => { const deviceOptions = []; let selectedDeviceFeaturesOptions = []; + const deviceThresholdOptions = []; + let selectedTresholdOptions = []; devices.forEach(device => { const deviceFeaturesOptions = []; + const deviceFeaturesTresholdOptions = []; device.features.forEach(feature => { const featureOption = { value: feature.selector, label: getDeviceFeatureName(this.props.intl.dictionary, device, feature) }; + const featureOptionTreshold = { + value: feature.selector, + label: getDeviceFeatureName(this.props.intl.dictionary, device, feature) + }; this.deviceFeatureBySelector.set(feature.selector, feature); // We don't support all devices for this view if (!FEATURES_THAT_ARE_NOT_COMPATIBLE[feature.type]) { @@ -240,6 +317,7 @@ class EditChart extends Component { deviceFeaturesOptions.push(featureOption); } else if (!FEATURE_BINARY[feature.type]) { deviceFeaturesOptions.push(featureOption); + deviceFeaturesTresholdOptions.push(featureOptionTreshold); } } // If the feature is already selected @@ -254,8 +332,30 @@ class EditChart extends Component { // And we push this to the list of selected feature selectedDeviceFeaturesOptions.push(featureOption); } + console.log('this.props.box', this.props.box); + console.log('feature.selector', feature.selector); + if (this.props.box.device_features_treshold) { + const featureTresholdIndex = this.props.box.device_features_treshold.indexOf(feature.selector); + if (featureTresholdIndex !== -1) { + selectedTresholdOptions.push(featureOptionTreshold); + } + } } }); + if (deviceFeaturesTresholdOptions.length > 0) { + deviceFeaturesTresholdOptions.sort((a, b) => { + if (a.label < b.label) { + return -1; + } else if (a.label > b.label) { + return 1; + } + return 0; + }); + deviceThresholdOptions.push({ + label: device.name, + options: deviceFeaturesTresholdOptions + }); + } if (deviceFeaturesOptions.length > 0) { deviceFeaturesOptions.sort((a, b) => { if (a.label < b.label) { @@ -300,7 +400,7 @@ class EditChart extends Component { (a, b) => this.props.box.device_features.indexOf(a.value) - this.props.box.device_features.indexOf(b.value) ); } - return { deviceOptions, selectedDeviceFeaturesOptions }; + return { deviceOptions, selectedDeviceFeaturesOptions, deviceThresholdOptions, selectedTresholdOptions }; }; getDeviceFeatures = async (chartType = this.state.chart_type) => { @@ -308,11 +408,11 @@ class EditChart extends Component { this.setState({ loading: true }); // we get the rooms with the devices const devices = await this.props.httpClient.get(`/api/v1/device`); - const { deviceOptions, selectedDeviceFeaturesOptions } = this.getSelectedDeviceFeaturesAndOptions( + const { deviceOptions, selectedDeviceFeaturesOptions, deviceThresholdOptions, selectedTresholdOptions } = this.getSelectedDeviceFeaturesAndOptions( devices, chartType ); - await this.setState({ devices, deviceOptions, selectedDeviceFeaturesOptions, loading: false }); + await this.setState({ devices, deviceOptions, selectedDeviceFeaturesOptions, deviceThresholdOptions, selectedTresholdOptions, loading: false }); this.refreshDeviceFeaturesNames(); } catch (e) { console.error(e); @@ -351,6 +451,7 @@ class EditChart extends Component { constructor(props) { super(props); this.props = props; + const box = this.props.box; this.deviceFeatureBySelector = new Map(); this.state = { chart_type: '', @@ -358,10 +459,28 @@ class EditChart extends Component { deviceOptions: [], loading: false, displayPreview: false, - chartTypeList: [...CHART_TYPE_BINARY, ...CHART_TYPE_OTHERS] + chartTypeList: [...CHART_TYPE_BINARY, ...CHART_TYPE_OTHERS], + selectedTresholdOptions: [], + manualThresholdDetails: box.manual_features_treshold_details || [], + editingIndex: null }; } + openEditForm = index => { + this.setState({ editingIndex: index }); + }; + + closeEditForm = () => { + // TODO: check if the detail is valid and error + const { manualThresholdDetails, editingIndex } = this.state; + const detail = manualThresholdDetails[editingIndex]; + if (detail.name && detail.value && !isNaN(detail.value) && detail.color && detail.unit) { + this.setState({ editingIndex: null }); + } else { + alert('Please fill in all fields correctly before closing.'); + } + }; + componentDidMount() { this.getDeviceFeatures(); } @@ -374,7 +493,7 @@ class EditChart extends Component { } } - render(props, { selectedDeviceFeaturesOptions, deviceOptions, loading, displayPreview, chartTypeList }) { + render(props, { selectedDeviceFeaturesOptions, selectedTresholdOptions, deviceOptions, deviceThresholdOptions, loading, displayPreview, manualThresholdDetails, editingIndex, chartTypeList }) { const manyFeatures = selectedDeviceFeaturesOptions && selectedDeviceFeaturesOptions.length > 1; const colorOptions = DEFAULT_COLORS.map((colorValue, i) => ({ value: colorValue, @@ -571,6 +690,65 @@ class EditChart extends Component { )}
+ {deviceThresholdOptions && ( +
+ +
+ updateManualThresholdDetail(index, 'name', e.target.value)} + /> +
+
+
+
+ + updateManualThresholdDetail(index, 'value', e.target.value)} + /> +
+
+ +
+
+
+ + +
+
+
+ {!isWithoutMoreOrLess && ( +
+ + updateManualThresholdDetail(index, 'value_more_or_less', e.target.value)} + /> +
+ )} +
+
+
+
+
+ + +
+
+
+
+ +