update Menu Report
This commit is contained in:
@@ -9,6 +9,8 @@ import {
|
|||||||
import { getAllPlantSection } from '../../../../api/master-plant-section';
|
import { getAllPlantSection } from '../../../../api/master-plant-section';
|
||||||
import jsPDF from 'jspdf';
|
import jsPDF from 'jspdf';
|
||||||
import autoTable from 'jspdf-autotable';
|
import autoTable from 'jspdf-autotable';
|
||||||
|
import ExcelJS from 'exceljs';
|
||||||
|
import { saveAs } from 'file-saver';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
@@ -54,9 +56,9 @@ const ListReport = memo(function ListReport(props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fetchData = async (page = 1, pageSize = 10, showModal = false) => {
|
const fetchData = async (page = 1, pageSize = 10, showModal = false) => {
|
||||||
if (!plantSubSection) {
|
// if (!plantSubSection) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (showModal) {
|
if (showModal) {
|
||||||
setIsLoadingModal(true);
|
setIsLoadingModal(true);
|
||||||
@@ -195,8 +197,34 @@ const ListReport = memo(function ListReport(props) {
|
|||||||
fetchData(pagination.current, pagination.pageSize, false);
|
fetchData(pagination.current, pagination.pageSize, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = async () => {
|
||||||
fetchData(1, pagination.pageSize, true);
|
setIsLoadingModal(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formattedDateStart = startDate.format('YYYY-MM-DD');
|
||||||
|
const formattedDateEnd = endDate.format('YYYY-MM-DD');
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
plant_sub_section_id: plantSubSection,
|
||||||
|
from: formattedDateStart,
|
||||||
|
to: formattedDateEnd,
|
||||||
|
interval: periode,
|
||||||
|
page: 1,
|
||||||
|
limit: 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pivotResponse = await getAllHistoryValueReportPivot(params);
|
||||||
|
|
||||||
|
// Jika response sukses, proses data
|
||||||
|
if (pivotResponse && pivotResponse.data) {
|
||||||
|
await fetchData(1, pagination.pageSize, false);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching data:', error);
|
||||||
|
// Error akan ditangkap oleh api-request.js dan muncul Swal otomatis
|
||||||
|
} finally {
|
||||||
|
setIsLoadingModal(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
@@ -247,6 +275,168 @@ const ListReport = memo(function ListReport(props) {
|
|||||||
{ value: 120, label: '2 Hour', disabled: false },
|
{ value: 120, label: '2 Hour', disabled: false },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const exportToExcel = async () => {
|
||||||
|
if (pivotData.length === 0) {
|
||||||
|
alert('No data to export');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagMapping = {};
|
||||||
|
valueReportData.forEach(item => {
|
||||||
|
if (item.tag_name && item.tag_number) {
|
||||||
|
tagMapping[item.tag_name] = item.tag_number;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedSection = plantSubSectionList.find(
|
||||||
|
item => item.plant_sub_section_id === plantSubSection
|
||||||
|
);
|
||||||
|
const sectionName = selectedSection ? selectedSection.plant_sub_section_name : 'Unknown';
|
||||||
|
|
||||||
|
// Buat struktur pivot yang sama seperti di tabel
|
||||||
|
const timeMap = new Map();
|
||||||
|
const tagSet = new Set();
|
||||||
|
|
||||||
|
pivotData.forEach((row) => {
|
||||||
|
const tagName = row.id;
|
||||||
|
tagSet.add(tagName);
|
||||||
|
|
||||||
|
const dataPoints = row.data || [];
|
||||||
|
dataPoints.forEach((item) => {
|
||||||
|
if (item && typeof item === 'object' && 'x' in item && 'y' in item) {
|
||||||
|
const datetime = item.x;
|
||||||
|
if (!timeMap.has(datetime)) {
|
||||||
|
timeMap.set(datetime, {});
|
||||||
|
}
|
||||||
|
timeMap.get(datetime)[tagName] = item.y;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortedTimes = Array.from(timeMap.keys()).sort();
|
||||||
|
const sortedTags = Array.from(tagSet).sort();
|
||||||
|
|
||||||
|
const pivotTableData = sortedTimes.map((datetime) => {
|
||||||
|
const rowData = {
|
||||||
|
datetime: datetime,
|
||||||
|
};
|
||||||
|
|
||||||
|
sortedTags.forEach((tagName) => {
|
||||||
|
rowData[tagName] = timeMap.get(datetime)[tagName];
|
||||||
|
});
|
||||||
|
|
||||||
|
return rowData;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Excel Pivot data:', pivotTableData.slice(0, 5));
|
||||||
|
console.log('Total rows for Excel:', pivotTableData.length);
|
||||||
|
|
||||||
|
const workbook = new ExcelJS.Workbook();
|
||||||
|
const ws = workbook.addWorksheet('Pivot Report');
|
||||||
|
|
||||||
|
// Buat header info (3 baris pertama)
|
||||||
|
ws.addRow(['PT. PUPUK INDONESIA UTILITAS']);
|
||||||
|
ws.addRow(['GRESIK GAS COGENERATION PLANT']);
|
||||||
|
ws.addRow([`${sectionName}`]);
|
||||||
|
ws.addRow([]); // Baris kosong sebagai pemisah
|
||||||
|
|
||||||
|
// Buat header kolom dengan tag number
|
||||||
|
const headerRow = [
|
||||||
|
'Datetime',
|
||||||
|
...sortedTags.map(tag => tagMapping[tag] || tag)
|
||||||
|
];
|
||||||
|
ws.addRow(headerRow);
|
||||||
|
|
||||||
|
// Buat data rows - PERBAIKAN: Simpan sebagai number murni
|
||||||
|
pivotTableData.forEach((rowData) => {
|
||||||
|
const row = [dayjs(rowData.datetime).format('DD-MM-YYYY HH:mm')];
|
||||||
|
sortedTags.forEach((tagName) => {
|
||||||
|
const value = rowData[tagName];
|
||||||
|
// Simpan sebagai number, bukan string
|
||||||
|
if (value !== undefined && value !== null) {
|
||||||
|
row.push(Number(value));
|
||||||
|
} else {
|
||||||
|
row.push('-');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ws.addRow(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set column widths
|
||||||
|
ws.getColumn(1).width = 18; // Datetime column
|
||||||
|
for (let i = 2; i <= sortedTags.length + 1; i++) {
|
||||||
|
ws.getColumn(i).width = 12; // Tag columns
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge cells untuk header info
|
||||||
|
const totalCols = sortedTags.length + 1;
|
||||||
|
ws.mergeCells(1, 1, 1, totalCols); // Baris 1
|
||||||
|
ws.mergeCells(2, 1, 2, totalCols); // Baris 2
|
||||||
|
ws.mergeCells(3, 1, 3, totalCols); // Baris 3
|
||||||
|
|
||||||
|
// Style untuk header info (3 baris pertama - bold dan center)
|
||||||
|
for (let i = 1; i <= 3; i++) {
|
||||||
|
const cell = ws.getCell(i, 1);
|
||||||
|
cell.font = { bold: true, size: 12 };
|
||||||
|
cell.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style untuk header kolom (bold, background color, center, border)
|
||||||
|
const headerRowIndex = 5; // Baris header
|
||||||
|
for (let col = 1; col <= totalCols; col++) {
|
||||||
|
const cell = ws.getCell(headerRowIndex, col);
|
||||||
|
cell.font = { bold: true, size: 11 };
|
||||||
|
cell.fill = {
|
||||||
|
type: 'pattern',
|
||||||
|
pattern: 'solid',
|
||||||
|
fgColor: { argb: 'FFDCDCDC' }
|
||||||
|
};
|
||||||
|
cell.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
|
||||||
|
cell.border = {
|
||||||
|
top: { style: 'thin', color: { argb: 'FF000000' } },
|
||||||
|
bottom: { style: 'thin', color: { argb: 'FF000000' } },
|
||||||
|
left: { style: 'thin', color: { argb: 'FF000000' } },
|
||||||
|
right: { style: 'thin', color: { argb: 'FF000000' } }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style untuk data cells (border dan alignment) - PERBAIKAN: Format number dengan 2 desimal
|
||||||
|
for (let row = headerRowIndex + 1; row <= ws.rowCount; row++) {
|
||||||
|
for (let col = 1; col <= totalCols; col++) {
|
||||||
|
const cell = ws.getCell(row, col);
|
||||||
|
|
||||||
|
cell.alignment = {
|
||||||
|
horizontal: 'center',
|
||||||
|
vertical: 'middle',
|
||||||
|
wrapText: true
|
||||||
|
};
|
||||||
|
cell.border = {
|
||||||
|
top: { style: 'thin', color: { argb: 'FF000000' } },
|
||||||
|
bottom: { style: 'thin', color: { argb: 'FF000000' } },
|
||||||
|
left: { style: 'thin', color: { argb: 'FF000000' } },
|
||||||
|
right: { style: 'thin', color: { argb: 'FF000000' } }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format number dengan 2 desimal untuk kolom value (kolom 2 dst)
|
||||||
|
if (col > 1) {
|
||||||
|
const cellValue = cell.value;
|
||||||
|
// Hanya set format number jika cell berisi angka
|
||||||
|
if (typeof cellValue === 'number') {
|
||||||
|
cell.numFmt = '0.00';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate file name
|
||||||
|
const fileName = `Report_Pivot_${startDate.format('DD-MM-YYYY')}_to_${endDate.format('DD-MM-YYYY')}.xlsx`;
|
||||||
|
|
||||||
|
// Save file
|
||||||
|
const buffer = await workbook.xlsx.writeBuffer();
|
||||||
|
const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||||
|
saveAs(blob, fileName);
|
||||||
|
};
|
||||||
|
|
||||||
const exportToPDF = async () => {
|
const exportToPDF = async () => {
|
||||||
if (pivotData.length === 0) {
|
if (pivotData.length === 0) {
|
||||||
alert('No data to export');
|
alert('No data to export');
|
||||||
@@ -393,7 +583,7 @@ const ListReport = memo(function ListReport(props) {
|
|||||||
doc.setFontSize(9);
|
doc.setFontSize(9);
|
||||||
doc.setFont('helvetica', 'bold');
|
doc.setFont('helvetica', 'bold');
|
||||||
doc.setFontSize(10);
|
doc.setFontSize(10);
|
||||||
doc.text(`Plant Section : ${sectionName}`, marginLeft + col1Width + col2Width / 2, 41, { align: 'center' });
|
doc.text(`${sectionName}`, marginLeft + col1Width + col2Width / 2, 38, { align: 'center' });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hitung total kolom tag chunks
|
// Hitung total kolom tag chunks
|
||||||
@@ -534,7 +724,7 @@ const ListReport = memo(function ListReport(props) {
|
|||||||
autoTable(doc, {
|
autoTable(doc, {
|
||||||
head: [headerRow],
|
head: [headerRow],
|
||||||
body: pdfRows,
|
body: pdfRows,
|
||||||
startY: isFirstPage ? 50 : 15,
|
startY: isFirstPage ? 43 : 15,
|
||||||
theme: 'grid',
|
theme: 'grid',
|
||||||
rowPageBreak: 'avoid',
|
rowPageBreak: 'avoid',
|
||||||
styles: {
|
styles: {
|
||||||
@@ -542,7 +732,7 @@ const ListReport = memo(function ListReport(props) {
|
|||||||
cellPadding: 1.5,
|
cellPadding: 1.5,
|
||||||
minCellHeight: 8,
|
minCellHeight: 8,
|
||||||
lineColor: [0, 0, 0],
|
lineColor: [0, 0, 0],
|
||||||
lineWidth: 0.1,
|
lineWidth: 0.5,
|
||||||
halign: 'center',
|
halign: 'center',
|
||||||
valign: 'middle',
|
valign: 'middle',
|
||||||
overflow: 'linebreak',
|
overflow: 'linebreak',
|
||||||
@@ -554,7 +744,7 @@ const ListReport = memo(function ListReport(props) {
|
|||||||
halign: 'center',
|
halign: 'center',
|
||||||
valign: 'middle',
|
valign: 'middle',
|
||||||
lineColor: [0, 0, 0],
|
lineColor: [0, 0, 0],
|
||||||
lineWidth: 0.3,
|
lineWidth: 0.5,
|
||||||
},
|
},
|
||||||
columnStyles: {
|
columnStyles: {
|
||||||
0: {
|
0: {
|
||||||
@@ -694,11 +884,23 @@ const ListReport = memo(function ListReport(props) {
|
|||||||
type="primary"
|
type="primary"
|
||||||
icon={<DownloadOutlined />}
|
icon={<DownloadOutlined />}
|
||||||
onClick={exportToPDF}
|
onClick={exportToPDF}
|
||||||
disabled={false}
|
disabled={pivotData.length === 0}
|
||||||
|
style={{ backgroundColor: '#1890ff', borderColor: '#1890ff' }}
|
||||||
>
|
>
|
||||||
Export PDF
|
Export PDF
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<DownloadOutlined />}
|
||||||
|
onClick={exportToExcel}
|
||||||
|
disabled={pivotData.length === 0}
|
||||||
|
style={{ backgroundColor: '#28a745', borderColor: '#28a745' }}
|
||||||
|
>
|
||||||
|
Export Excel
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
<Col>
|
<Col>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
|
|||||||
@@ -130,9 +130,26 @@ const ReportTrending = memo(function ReportTrending(props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Fungsi untuk menentukan apakah rentang tanggal lebih dari 1 hari
|
||||||
|
const isMultipleDays = () => {
|
||||||
|
return !startDate.isSame(endDate, 'day');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Format sumbu X yang otomatis menyesuaikan
|
||||||
const formatXAxis = (tickItem) => {
|
const formatXAxis = (tickItem) => {
|
||||||
const date = new Date(tickItem);
|
const date = new Date(tickItem);
|
||||||
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
const hours = date.getHours().toString().padStart(2, '0');
|
||||||
|
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||||
|
|
||||||
|
// Jika rentang lebih dari 1 hari, tampilkan tanggal + waktu
|
||||||
|
if (isMultipleDays()) {
|
||||||
|
const day = date.getDate().toString().padStart(2, '0');
|
||||||
|
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||||
|
return `${day}/${month} ${hours}:${minutes}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika hanya 1 hari, tampilkan waktu saja
|
||||||
|
return `${hours}:${minutes}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CustomTooltip = ({ active, payload, label }) => {
|
const CustomTooltip = ({ active, payload, label }) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user