300 lines
14 KiB
JavaScript
300 lines
14 KiB
JavaScript
import React, { memo, useState, useEffect } from 'react';
|
|
import { Button, Row, Col, Card, Input, DatePicker, Select, Typography } from 'antd';
|
|
import dayjs from 'dayjs';
|
|
import { FileTextOutlined } from '@ant-design/icons';
|
|
import { ResponsiveLine } from '@nivo/line';
|
|
import './trending.css';
|
|
import { getAllPlantSection } from '../../../api/master-plant-section';
|
|
import { getAllHistoryValueTrendingPivot } from '../../../api/history-value';
|
|
|
|
const { Text } = Typography;
|
|
|
|
const ReportTrending = memo(function ReportTrending(props) {
|
|
const dateNow = dayjs();
|
|
const dateNowFormated = dateNow.format('YYYY-MM-DD');
|
|
|
|
const [plantSubSection, setPlantSubSection] = useState(0);
|
|
const [plantSubSectionList, setPlantSubSectionList] = useState([]);
|
|
const [startDate, setStartDate] = useState(dateNow);
|
|
const [endDate, setEndDate] = useState(dateNow);
|
|
const [periode, setPeriode] = useState(60);
|
|
|
|
const defaultFilter = {
|
|
criteria: '',
|
|
plant_sub_section_id: plantSubSection,
|
|
from: dateNowFormated,
|
|
to: dateNowFormated,
|
|
interval: periode,
|
|
};
|
|
const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
|
|
|
|
const [trendingValue, setTrendingValue] = useState([]);
|
|
|
|
const handleSearch = async () => {
|
|
const formattedDateStart = startDate.format('YYYY-MM-DD');
|
|
const formattedDateEnd = endDate.format('YYYY-MM-DD');
|
|
|
|
const newFilter = {
|
|
criteria: '',
|
|
plant_sub_section_id: plantSubSection,
|
|
from: formattedDateStart,
|
|
to: formattedDateEnd,
|
|
interval: periode,
|
|
};
|
|
|
|
setFormDataFilter(newFilter);
|
|
|
|
const param = new URLSearchParams(newFilter);
|
|
const response = await getAllHistoryValueTrendingPivot(param);
|
|
|
|
if (response?.data?.length > 0) {
|
|
// 🔹 Bersihkan dan format data agar aman untuk Nivo
|
|
const cleanedData = response.data.map((serie) => ({
|
|
id: serie.id ?? 'Unknown',
|
|
data: Array.isArray(serie.data)
|
|
? serie.data.map((d) => ({
|
|
x: d?.x ?? null,
|
|
y:
|
|
d?.y !== null && d?.y !== undefined
|
|
? Number(d.y).toFixed(4) // format 4 angka di belakang koma
|
|
: null,
|
|
}))
|
|
: [],
|
|
}));
|
|
|
|
setTrendingValue(cleanedData);
|
|
} else {
|
|
// 🔹 Jika tidak ada data dari API
|
|
setTrendingValue([]);
|
|
}
|
|
};
|
|
|
|
const handleReset = () => {
|
|
setPlantSubSection(0);
|
|
setStartDate(dateNow);
|
|
setEndDate(dateNow);
|
|
setPeriode(5);
|
|
};
|
|
|
|
const getPlantSubSection = async () => {
|
|
const params = new URLSearchParams({ page: 1 });
|
|
const response = await getAllPlantSection(params);
|
|
|
|
if (response && response.data) {
|
|
const activePlantSubSections = response.data.filter(
|
|
(section) => section.is_active === true
|
|
);
|
|
setPlantSubSectionList(activePlantSubSections);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
getPlantSubSection();
|
|
}, []);
|
|
|
|
return (
|
|
<React.Fragment>
|
|
<Card>
|
|
<Row>
|
|
<Col xs={24}>
|
|
<Row gutter={16} style={{ marginTop: '16px' }}>
|
|
<Col xs={24} sm={12} md={6}>
|
|
<div className="filter-item">
|
|
<Text style={{ fontSize: '12px', color: '#666' }}>
|
|
Plant Sub Section
|
|
</Text>
|
|
<Select
|
|
value={plantSubSection}
|
|
onChange={(value) => setPlantSubSection(value)}
|
|
style={{ width: '100%', marginTop: '4px' }}
|
|
>
|
|
<Select.Option key={0} value={0}>
|
|
Pilih Plant Sub Section
|
|
</Select.Option>
|
|
{plantSubSectionList.map((item) => (
|
|
<Select.Option
|
|
key={item.plant_sub_section_id}
|
|
value={item.plant_sub_section_id}
|
|
>
|
|
{item.plant_sub_section_name}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</div>
|
|
</Col>
|
|
<Col xs={24} sm={12} md={6}>
|
|
<div className="filter-item">
|
|
<Text style={{ fontSize: '12px', color: '#666' }}>
|
|
Tanggal Mulai
|
|
</Text>
|
|
<DatePicker
|
|
value={startDate}
|
|
onChange={setStartDate}
|
|
format="DD-MM-YYYY"
|
|
style={{ width: '100%', marginTop: '4px' }}
|
|
/>
|
|
</div>
|
|
</Col>
|
|
<Col xs={24} sm={12} md={6}>
|
|
<div className="filter-item">
|
|
<Text style={{ fontSize: '12px', color: '#666' }}>
|
|
Tanggal Akhir
|
|
</Text>
|
|
<DatePicker
|
|
value={endDate}
|
|
onChange={setEndDate}
|
|
format="DD-MM-YYYY"
|
|
style={{ width: '100%', marginTop: '4px' }}
|
|
/>
|
|
</div>
|
|
</Col>
|
|
<Col xs={24} sm={12} md={6}>
|
|
<div className="filter-item">
|
|
<Text style={{ fontSize: '12px', color: '#666' }}>Periode</Text>
|
|
<Select
|
|
value={periode}
|
|
onChange={setPeriode}
|
|
style={{ width: '100%', marginTop: '4px' }}
|
|
options={[
|
|
{ value: 5, label: '5 Minute' },
|
|
{ value: 10, label: '10 Minute' },
|
|
{ value: 30, label: '30 Minute' },
|
|
{ value: 60, label: '1 Hour' },
|
|
{ value: 120, label: '2 Hour' },
|
|
]}
|
|
></Select>
|
|
</div>
|
|
</Col>
|
|
</Row>
|
|
<Row gutter={8} style={{ marginTop: '16px' }}>
|
|
<Col>
|
|
<Button
|
|
type="primary"
|
|
danger
|
|
icon={<FileTextOutlined />}
|
|
onClick={handleSearch}
|
|
>
|
|
Show
|
|
</Button>
|
|
</Col>
|
|
<Col>
|
|
<Button
|
|
onClick={handleReset}
|
|
style={{ backgroundColor: '#6c757d', color: 'white' }}
|
|
>
|
|
Reset
|
|
</Button>
|
|
</Col>
|
|
</Row>
|
|
</Col>
|
|
<Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: '16px' }}>
|
|
<div style={{ height: '500px', marginTop: '16px' }}>
|
|
{trendingValue && trendingValue.length > 0 ? (
|
|
<ResponsiveLine
|
|
data={trendingValue} // [{ id, data: [{x, y}] }]
|
|
// data={
|
|
// trendingValue && trendingValue.length
|
|
// ? trendingValue
|
|
// : [{ id, data: [{ x, y }] }]
|
|
// }
|
|
margin={{ top: 40, right: 100, bottom: 70, left: 70 }}
|
|
xScale={{
|
|
type: 'time',
|
|
format: '%Y-%m-%d %H:%M',
|
|
useUTC: false,
|
|
precision: 'minute',
|
|
}}
|
|
xFormat="time:%Y-%m-%d %H:%M"
|
|
yScale={{
|
|
type: 'linear',
|
|
min: 'auto',
|
|
max: 'auto',
|
|
stacked: false,
|
|
reverse: false,
|
|
}}
|
|
yFormat={(value) => Number(value).toFixed(4)} // ✅ format 4 angka di belakang koma
|
|
axisBottom={{
|
|
format: '%Y-%m-%d %H:%M', // ✅ tampilkan tanggal + jam
|
|
tickValues: 'every 2 hours', // tampilkan setiap 2 jam (bisa ubah ke every 30 minutes)
|
|
tickSize: 5,
|
|
tickPadding: 5,
|
|
tickRotation: -45,
|
|
legend: 'Tanggal & Waktu',
|
|
legendOffset: 60,
|
|
legendPosition: 'middle',
|
|
}}
|
|
axisLeft={{
|
|
tickSize: 5,
|
|
tickPadding: 5,
|
|
tickRotation: 0,
|
|
legend: 'Nilai (Avg)',
|
|
legendOffset: -60,
|
|
legendPosition: 'middle',
|
|
format: (value) => Number(value).toFixed(4), // ✅ tampilkan 4 angka di sumbu Y
|
|
}}
|
|
curve="monotoneX"
|
|
colors={{ scheme: 'category10' }}
|
|
pointSize={6}
|
|
pointColor={{ theme: 'background' }}
|
|
pointBorderWidth={2}
|
|
pointBorderColor={{ from: 'serieColor' }}
|
|
enablePointLabel={false}
|
|
enableGridX={true}
|
|
enableGridY={true}
|
|
useMesh={true}
|
|
tooltip={({ point }) => (
|
|
<div
|
|
style={{
|
|
background: 'white',
|
|
padding: '6px 9px',
|
|
border: '1px solid #ccc',
|
|
borderRadius: '6px',
|
|
}}
|
|
>
|
|
<strong>{point.serieId}</strong>
|
|
<br />
|
|
{point.data.xFormatted}
|
|
<br />
|
|
<span style={{ color: point.serieColor }}>
|
|
{Number(point.data.y).toFixed(4)}
|
|
</span>
|
|
</div>
|
|
)}
|
|
legends={[
|
|
{
|
|
anchor: 'bottom-right',
|
|
direction: 'column',
|
|
justify: false,
|
|
translateX: 100,
|
|
translateY: 0,
|
|
itemsSpacing: 2,
|
|
itemDirection: 'left-to-right',
|
|
itemWidth: 120,
|
|
itemHeight: 20,
|
|
itemOpacity: 0.85,
|
|
symbolSize: 12,
|
|
symbolShape: 'circle',
|
|
},
|
|
]}
|
|
/>
|
|
) : (
|
|
<div
|
|
style={{
|
|
textAlign: 'center',
|
|
marginTop: '40px',
|
|
color: '#999',
|
|
}}
|
|
>
|
|
Tidak ada data untuk ditampilkan
|
|
</div>
|
|
)}
|
|
</div>
|
|
</Col>
|
|
</Row>
|
|
</Card>
|
|
</React.Fragment>
|
|
);
|
|
});
|
|
|
|
export default ReportTrending;
|