Passed
Push — master ( 2e4a80...c9c6f2 )
by
unknown
09:47
created

RealtimeData.componentDidMount   A

Complexity

Conditions 1

Size

Total Lines 8
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 1
1
import React, { Component } from 'react';
2
import { Card, CardHeader, CardBody, ListGroup, ListGroupItem } from 'reactstrap';
3
import { withTranslation } from 'react-i18next';
4
import { v4 as uuid } from 'uuid';
5
import { Line } from 'react-chartjs-2';
6
import { Chart } from 'chart.js';
7
import {
8
  LineController, LineElement, PointElement,
9
  LinearScale, Title, Tooltip, Legend, CategoryScale
10
} from 'chart.js';
11
import { APIBaseURL } from '../../../config';
12
import { getCookieValue } from '../../../helpers/utils';
13
import { toast } from 'react-toastify';
14
15
Chart.register(
16
  LineController, LineElement, PointElement,
17
  LinearScale, CategoryScale, Title, Tooltip, Legend
18
);
19
20
const dividerBorder = '1px solid rgba(0, 0, 0, 0.05)';
21
const listItemBorderColor = 'rgba(0, 0, 0, 0.05)';
22
23
class RealtimeData extends Component {
24
  _isMounted = false;
25
  refreshInterval;
26
  state = {
27
    pointList: [],
28
    trendData: {}
29
  };
30
31
  componentWillUnmount() {
32
    this._isMounted = false;
33
    clearInterval(this.refreshInterval);
34
  }
35
36
  componentDidMount() {
37
    this._isMounted = true;
38
    this.fetchData();
39
40
    this.refreshInterval = setInterval(() => {
41
      this.fetchData();
42
    }, (60 + Math.floor(Math.random() * Math.floor(10))) * 1000);
43
  }
44
45
  fetchData = async () => {
46
    try {
47
      const response = await fetch(
48
        `${APIBaseURL}/reports/spaceenvironmentmonitor?sensorid=${this.props.sensorId}&timerange=24h`,
49
        {
50
          method: 'GET',
51
          headers: {
52
            'Content-type': 'application/json',
53
            'User-UUID': getCookieValue('user_uuid'),
54
            'Token': getCookieValue('token')
55
          }
56
        }
57
      );
58
59
      if (!response.ok) throw new Error('Data fetch failed');
60
      const json = await response.json();
61
62
      const pointList = [];
63
      const trendData = {};
64
65
      if (json['energy_value']) {
66
        pointList.push({
67
          name: json['energy_value'].name,
68
          value: json['energy_value'].values.length > 0
69
            ? json['energy_value'].values[json['energy_value'].values.length - 1]
70
            : undefined
71
        });
72
        trendData[json['energy_value'].name] = {
73
          values: json['energy_value'].values,
74
          timestamps: json['energy_value'].timestamps.map(ts => ts.substring(11, 16))
75
        };
76
      }
77
78
      json['parameters']['names'].forEach((name, index) => {
79
        const values = json['parameters']['values'][index];
80
        const timestamps = json['parameters']['timestamps'][index].map(ts => ts.substring(11, 16));
81
82
        pointList.push({
83
          name,
84
          value: values.length > 0 ? values[values.length - 1] : undefined
85
        });
86
87
        trendData[name] = { values, timestamps };
88
      });
89
90
      if (this._isMounted) {
91
        this.setState({ pointList, trendData });
92
      }
93
    } catch (err) {
94
      console.error('Realtime data fetch error:', err);
95
      toast.error(this.props.t(err.message || 'Data fetch failed'));
96
    }
97
  };
98
99
  sampleData = (data, maxPoints = 24) => {
100
    if (data.length <= maxPoints) return data;
101
102
    const step = Math.ceil(data.length / maxPoints);
103
    return data.filter((_, index) => index % step === 0);
104
  };
105
106
  renderTrendChart = (name, data) => {
107
    const colors = ['#36A2EB', '#4BC0C0', '#FF9F40', '#9966FF', '#FF6384', '#00CC99'];
108
    const colorIndex = Object.keys(this.state.trendData).indexOf(name) % colors.length;
109
110
    const sampledValues = this.sampleData(data.values, 24);
111
    const sampledTimestamps = this.sampleData(data.timestamps, 24);
112
113
    return (
114
      <Line
115
        data={{
116
          labels: sampledTimestamps,
117
          datasets: [{
118
            label: name,
119
            data: sampledValues,
120
            borderColor: colors[colorIndex],
121
            backgroundColor: `${colors[colorIndex]}20`,
122
            borderWidth: 2,
123
            pointRadius: 1.5,
124
            pointHoverRadius: 3,
125
            tension: 0.4,
126
            fill: true
127
          }]
128
        }}
129
        options={{
130
          responsive: true,
131
          maintainAspectRatio: false,
132
          plugins: {
133
            legend: { display: false },
134
            tooltip: {
135
              mode: 'index',
136
              intersect: false,
137
              backgroundColor: 'rgba(0, 0, 0, 0.7)',
138
              titleFont: { size: 10 },
139
              bodyFont: { size: 10 }
140
            }
141
          },
142
          scales: {
143
            x: {
144
              display: false
145
            },
146
            y: {
147
              display: false,
148
              beginAtZero: false
149
            }
150
          },
151
          interaction: {
152
            mode: 'nearest',
153
            axis: 'x',
154
            intersect: false
155
          }
156
        }}
157
        height={50}
158
      />
159
    );
160
  };
161
162
  render() {
163
    const { t, isActive, onClick } = this.props;
164
165
    return (
166
      <Card
167
        className={`h-100 shadow-sm cursor-pointer ${isActive ? 'border-primary' : 'border-light'}`}
168
        onClick={onClick}
169
      >
170
        <CardHeader className="bg-white border-bottom py-3">
171
          <h6 className="mb-0">{this.props.sensorName}</h6>
172
        </CardHeader>
173
        <CardBody className="p-2">
174
          <ListGroup flush className="mt-1">
175
            <ListGroupItem
176
              className="bg-transparent font-weight-bold border-top-0 py-1"
177
              style={{ borderColor: listItemBorderColor }}
178
            >
179
              <div className="d-flex justify-content-between">
180
                <span className="text-muted">{t('Point')}</span>
181
                <span className="text-muted">{t('Value')}</span>
182
              </div>
183
            </ListGroupItem>
184
            {this.state.pointList.map((item, index) => (
185
              <ListGroupItem
186
                key={uuid()}
187
                className="bg-transparent d-flex flex-column justify-content-between py-1"
188
                style={{ borderColor: listItemBorderColor }}
189
              >
190
                <div className="d-flex justify-content-between align-items-center mb-0">
191
                  <span>{item.name}</span>
192
                  <span className="font-weight-bold text-primary">{item.value}</span>
193
                </div>
194
                {this.state.trendData[item.name] && (
195
                  <div className="mt-0">
196
                    {this.renderTrendChart(item.name, this.state.trendData[item.name])}
197
                  </div>
198
                )}
199
              </ListGroupItem>
200
            ))}
201
          </ListGroup>
202
        </CardBody>
203
      </Card>
204
    );
205
  }
206
}
207
208
export default withTranslation()(RealtimeData);