diff --git a/src/api/history-value.jsx b/src/api/history-value.jsx index b96f39a..2dafb3a 100644 --- a/src/api/history-value.jsx +++ b/src/api/history-value.jsx @@ -18,4 +18,37 @@ const getAllHistoryEvent = async (queryParams) => { return response.data; }; -export { getAllHistoryAlarm, getAllHistoryEvent }; +const getAllHistoryValueReport = async (queryParams) => { + const response = await SendRequest({ + method: 'get', + prefix: `history/value-report?${queryParams.toString()}`, + }); + + return response.data; +}; + +const getAllHistoryValueReportPivot = async (queryParams) => { + const response = await SendRequest({ + method: 'get', + prefix: `history/value-report-pivot?${queryParams.toString()}`, + }); + + return response.data; +}; + +const getAllHistoryValueTrendingPivot = async (queryParams) => { + const response = await SendRequest({ + method: 'get', + prefix: `history/value-trending?${queryParams.toString()}`, + }); + + return response.data; +}; + +export { + getAllHistoryAlarm, + getAllHistoryEvent, + getAllHistoryValueReport, + getAllHistoryValueReportPivot, + getAllHistoryValueTrendingPivot, +}; diff --git a/src/components/Global/CardList.jsx b/src/components/Global/CardList.jsx index 262b8ba..bf8f373 100644 --- a/src/components/Global/CardList.jsx +++ b/src/components/Global/CardList.jsx @@ -57,22 +57,28 @@ const CardList = ({ } style={getCardStyle(fieldColor ? item[fieldColor] : cardColor)} actions={[ - showPreviewModal(item)} - />, - + ), + showEditModal && ( + showEditModal(item)} - />, - + ), + showDeleteDialog && ( + showDeleteDialog(item)} - />, - ]} + /> + ), + ].filter(Boolean)} // <== Hapus elemen yang undefined >
{column.map((itemCard, index) => ( diff --git a/src/components/Global/TableList.jsx b/src/components/Global/TableList.jsx index f8c0f2c..c5e30a3 100644 --- a/src/components/Global/TableList.jsx +++ b/src/components/Global/TableList.jsx @@ -1,6 +1,6 @@ import React, { memo, useState, useEffect, useRef } from 'react'; import { Table, Pagination, Row, Col, Card, Grid, Button, Typography, Tag, Segmented } from 'antd'; -import { AppstoreOutlined, TableOutlined } from '@ant-design/icons'; +import { MacCommandOutlined, TableOutlined } from '@ant-design/icons'; import CardList from './CardList'; const { Text } = Typography; @@ -18,6 +18,7 @@ const TableList = memo(function TableList({ showDeleteDialog, cardColor, fieldColor, + firstLoad = true, }) { const [gridLoading, setGridLoading] = useState(false); @@ -30,12 +31,19 @@ const TableList = memo(function TableList({ total_page: 1, }); - const [viewMode, setViewMode] = useState('list'); + const [viewMode, setViewMode] = useState('table'); const { useBreakpoint } = Grid; + const [renderCount, setRenderCount] = useState(firstLoad ? 1 : 0); + useEffect(() => { - filter(1, pagination.current_limit); + if (renderCount < 1) { + setRenderCount(renderCount + 1); + return; + } else { + filter(1, pagination.current_limit); + } }, [triger]); const filter = async (currentPage, pageSize) => { @@ -90,8 +98,8 @@ const TableList = memo(function TableList({
}, { value: 'table', icon: }, + { value: 'card', icon: }, ]} value={viewMode} onChange={setViewMode} @@ -116,6 +124,7 @@ const TableList = memo(function TableList({ pagination={false} loading={gridLoading} scroll={{ y: 520 }} + size="small" /> )} diff --git a/src/pages/report/report/component/ListReport.jsx b/src/pages/report/report/component/ListReport.jsx index 974af15..e5df4ff 100644 --- a/src/pages/report/report/component/ListReport.jsx +++ b/src/pages/report/report/component/ListReport.jsx @@ -3,6 +3,8 @@ import { Button, Row, Col, Card, Input, DatePicker, Select, Typography } from 'a import TableList from '../../../../components/Global/TableList'; import dayjs from 'dayjs'; import { FileTextOutlined } from '@ant-design/icons'; +import { getAllHistoryValueReport } from '../../../../api/history-value'; +import { getAllPlantSection } from '../../../../api/master-plant-section'; const { Text } = Typography; @@ -19,7 +21,7 @@ const ListReport = memo(function ListReport(props) { title: 'Datetime', dataIndex: 'datetime', key: 'datetime', - width: '10%', + width: '15%', }, { title: 'Tag Name', @@ -32,38 +34,73 @@ const ListReport = memo(function ListReport(props) { dataIndex: 'val', key: 'val', width: '10%', + render: (_, record) => Number(record.val).toFixed(4), }, { title: 'Stat', - dataIndex: 'stat', - key: 'stat', + dataIndex: 'status', + key: 'status', width: '10%', }, ]; + const dateNow = dayjs(); + const dateNowFormated = dateNow.format('YYYY-MM-DD'); + const [trigerFilter, setTrigerFilter] = useState(false); - const defaultFilter = { search: '' }; + const defaultFilter = { + criteria: '', + plant_sub_section_id: 0, + from: dateNowFormated, + to: dateNowFormated, + interval: 10, + }; const [formDataFilter, setFormDataFilter] = useState(defaultFilter); - const [plantSubSection, setPlantSubSection] = useState('Semua Plant'); - const [startDate, setStartDate] = useState(dayjs('2025-09-30')); - const [endDate, setEndDate] = useState(dayjs('2025-10-09')); - const [periode, setPeriode] = useState('10 Menit'); + const [plantSubSection, setPlantSubSection] = useState(0); + const [plantSubSectionList, setPlantSubSectionList] = useState([]); + const [startDate, setStartDate] = useState(dateNow); + const [endDate, setEndDate] = useState(dateNow); + const [periode, setPeriode] = useState(5); - const getAllReport = async (params) => { - return { - data: [], - }; + const handleSearch = () => { + const formattedDateStart = startDate.format('YYYY-MM-DD'); + const formattedDateEnd = endDate.format('YYYY-MM-DD'); + + setFormDataFilter({ + criteria: '', + plant_sub_section_id: plantSubSection, + from: formattedDateStart, + to: formattedDateEnd, + interval: periode, + }); + setTrigerFilter((prev) => !prev); }; const handleReset = () => { - setPlantSubSection('Semua Plant'); - setStartDate(dayjs('2025-09-30')); - setEndDate(dayjs('2025-10-09')); - setPeriode('10 Menit'); + 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 ( @@ -77,14 +114,21 @@ const ListReport = memo(function ListReport(props) {
@@ -95,7 +139,7 @@ const ListReport = memo(function ListReport(props) {
@@ -108,7 +152,7 @@ const ListReport = memo(function ListReport(props) { @@ -121,18 +165,24 @@ const ListReport = memo(function ListReport(props) { onChange={setPeriode} style={{ width: '100%', marginTop: '4px' }} options={[ - { value: '5 Menit', label: '5 Menit' }, - { value: '10 Menit', label: '10 Menit' }, - { value: '30 Menit', label: '30 Menit' }, - { value: '1 Jam', label: '1 Jam' }, + { 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' }, ]} - /> + > - @@ -148,7 +198,8 @@ const ListReport = memo(function ListReport(props) { { const token = localStorage.getItem('token'); diff --git a/src/pages/report/trending/ReportTrending.jsx b/src/pages/report/trending/ReportTrending.jsx index 04a1eb5..571801a 100644 --- a/src/pages/report/trending/ReportTrending.jsx +++ b/src/pages/report/trending/ReportTrending.jsx @@ -4,75 +4,94 @@ 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 tagTrendingData = [ - { - id: 'TEMP_SENSOR_1', - color: '#FF6B4A', - data: [ - { y: '08:00', x: 75 }, - { y: '08:05', x: 76 }, - { y: '08:10', x: 75 }, - { y: '08:15', x: 77 }, - { y: '08:20', x: 76 }, - { y: '08:25', x: 78 }, - { y: '08:30', x: 79 }, - ], - }, - { - id: 'GAS_LEAK_SENSOR_1', - color: '#4ECDC4', - data: [ - { y: '08:00', x: 10 }, - { y: '08:05', x: 150 }, - { y: '08:10', x: 40 }, - { y: '08:15', x: 20 }, - { y: '08:20', x: 15 }, - { y: '08:25', x: 18 }, - { y: '08:30', x: 25 }, - ], - }, - { - id: 'PRESSURE_SENSOR_1', - color: '#FFE66D', - data: [ - { y: '08:00', x: 1.2 }, - { y: '08:05', x: 1.3 }, - { y: '08:10', x: 1.2 }, - { y: '08:15', x: 1.4 }, - { y: '08:20', x: 1.5 }, - { y: '08:25', x: 1.3 }, - { y: '08:30', x: 1.2 }, - ], - }, -]; - const ReportTrending = memo(function ReportTrending(props) { - const [trigerFilter, setTrigerFilter] = useState(false); + const dateNow = dayjs(); + const dateNowFormated = dateNow.format('YYYY-MM-DD'); - const defaultFilter = { search: '' }; + 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 [plantSubSection, setPlantSubSection] = useState('Semua Plant'); - const [startDate, setStartDate] = useState(dayjs('2025-09-30')); - const [endDate, setEndDate] = useState(dayjs('2025-10-09')); - const [periode, setPeriode] = useState('10 Menit'); + const [trendingValue, setTrendingValue] = useState([]); - const getAllReport = async (params) => { - return { - data: [], + 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('Semua Plant'); - setStartDate(dayjs('2025-09-30')); - setEndDate(dayjs('2025-10-09')); - setPeriode('10 Menit'); + 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 ( @@ -86,14 +105,21 @@ const ReportTrending = memo(function ReportTrending(props) { @@ -104,7 +130,7 @@ const ReportTrending = memo(function ReportTrending(props) { @@ -117,7 +143,7 @@ const ReportTrending = memo(function ReportTrending(props) { @@ -130,18 +156,24 @@ const ReportTrending = memo(function ReportTrending(props) { onChange={setPeriode} style={{ width: '100%', marginTop: '4px' }} options={[ - { value: '5 Menit', label: '5 Menit' }, - { value: '10 Menit', label: '10 Menit' }, - { value: '30 Menit', label: '30 Menit' }, - { value: '1 Jam', label: '1 Jam' }, + { 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' }, ]} - /> + > - @@ -157,60 +189,105 @@ const ReportTrending = memo(function ReportTrending(props) {
- + {trendingValue && trendingValue.length > 0 ? ( + 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 }) => ( +
+ {point.serieId} +
+ {point.data.xFormatted} +
+ + {Number(point.data.y).toFixed(4)} + +
+ )} + 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', + }, + ]} + /> + ) : ( +
+ Tidak ada data untuk ditampilkan +
+ )}