Files
cod-fe/src/pages/report/trending/ReportTrending.jsx

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;