bast_frontend/src/pages/Laporan.tsx

223 lines
8.4 KiB
TypeScript

// src/pages/Laporan.tsx
import {
FileText,
Download,
User,
MapPin,
Layers,
BadgeCheck,
} from "lucide-react";
import { useEffect, useState } from "react";
import axios from "axios";
import jsPDF from "jspdf";
import autoTable from "jspdf-autotable";
import { FaHourglassEnd } from "react-icons/fa";
import { FaBox } from "react-icons/fa";
export default function Laporan() {
/* =======================================================
STATE DATA LAPORAN (Diberikan default array kosong [])
======================================================= */
const [dataLaporan, setDataLaporan] = useState<any[]>([]);
const [loading, setLoading] = useState<boolean>(true);
/* =======================================================
GET DATA DATABASE (Ambil data dari endpoint /api/bast)
======================================================= */
useEffect(() => {
setLoading(true);
axios
.get("http://localhost:5000/api/bast")
.then((res) => {
// PENGAMAN JALUR DATA: Memastikan data yang masuk ke state selalu bertipe Array
if (res.data && res.data.success && Array.isArray(res.data.data)) {
setDataLaporan(res.data.data);
} else if (Array.isArray(res.data)) {
setDataLaporan(res.data);
} else {
setDataLaporan([]);
}
})
.catch((err) => {
console.error("Gagal memuat data laporan:", err);
setDataLaporan([]);
})
.finally(() => {
setLoading(false);
});
}, []);
/* =======================================================
DOWNLOAD PDF LAPORAN KESELURUHAN (Sinkron Kolom Database)
======================================================= */
const downloadPDF = () => {
const doc = new jsPDF("l", "mm", "a4"); // Menggunakan mode Landscape biar muat banyak kolom
/* JUDUL DOKUMEN LAPORAN */
doc.setFont("helvetica", "bold");
doc.setFontSize(18);
doc.text("LAPORAN DATA TRANSAKSI BAST", 148, 20, { align: "center" });
doc.setFont("helvetica", "normal");
doc.setFontSize(10);
doc.text(`Dicetak pada: ${new Date().toLocaleDateString("id-ID")}`, 20, 28);
/* GENERATE MASTER TABLE LAPORAN */
autoTable(doc, {
startY: 33,
styles: {
fontSize: 9,
halign: "center",
valign: "middle",
lineWidth: 0.3,
lineColor: [180, 180, 180],
},
headStyles: {
fillColor: [41, 128, 185], // Warna biru premium standar industri
textColor: [255, 255, 255],
fontStyle: "bold",
},
head: [[
"No",
"ID BAST",
"Kode Barang",
"Nama Barang",
"Kategori",
"Lokasi",
"Penyunting/Penyerah",
"Penerima Aset",
"Tanggal Dibuat",
"Status Serah",
"Status Terima"
]],
body: dataLaporan.map((item, index) => [
index + 1,
`#${item.bast_id}`,
item.kode_barang || "-",
item.nama_barang || "-",
item.nama_kategori || "-",
item.nama_lokasi || "-",
item.nama_penyerah || "-",
item.nama_penerima || "-",
item.dibuat_pada ? new Date(item.dibuat_pada).toLocaleDateString("id-ID") : "-",
item.status_serah || "Menunggu",
item.status_terima || "Menunggu"
]),
});
/* SAVE FILE PDF LAPORAN */
doc.save(`Laporan_Tahunan_BAST_${Date.now()}.pdf`);
};
return (
<div className="p-8 min-h-screen bg-gray-100 dark:bg-gray-900 text-black dark:text-white transition-all duration-300">
{/* HEADER UTAMA */}
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-8">
<div>
<h1 className="text-4xl font-bold">Laporan</h1>
<p className="text-gray-500 dark:text-gray-300 mt-2">
Rekapitulasi data laporan mutasi transaksi BAST sekolah & CIFO.
</p>
</div>
{/* BUTTON GENERATE PDF */}
<button
onClick={downloadPDF}
disabled={dataLaporan.length === 0}
className={`flex items-center gap-2 bg-blue-600 hover:bg-blue-700 text-white px-5 py-3 rounded-2xl shadow-lg transition ${
dataLaporan.length === 0 ? "opacity-50 cursor-not-allowed" : ""
}`}
>
<Download size={20} />
Download Rekap PDF
</button>
</div>
{/* RENDER KONDISIONAL KARTU LAPORAN */}
{loading ? (
<div className="p-12 text-center bg-white dark:bg-gray-800 rounded-3xl text-blue-600 font-medium shadow animate-pulse">
Sedang menarik data rekapan laporan dari server... <FaHourglassEnd />
</div>
) : dataLaporan && dataLaporan.length > 0 ? (
<div className="space-y-5">
{dataLaporan.map((item) => (
<div
key={item.bast_id}
className="bg-white dark:bg-gray-800 p-6 rounded-3xl shadow-md border border-gray-100 dark:border-gray-700 hover:shadow-lg transition-all duration-300"
>
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
<div className="space-y-2 flex-1">
{/* ID DOKUMEN */}
<div className="flex items-center gap-2">
<FileText size={20} className="text-blue-600 shrink-0" />
<h2 className="text-xl font-extrabold dark:text-white">
ID BAST #{item.bast_id}
</h2>
<span className="text-xs font-mono font-bold ml-2 px-2 py-0.5 bg-gray-100 dark:bg-gray-700 text-gray-500 rounded">
{item.kode_barang || "BLANK"}
</span>
</div>
{/* NAMA BARANG */}
<p className="text-lg font-bold text-blue-600 dark:text-blue-400">
{item.nama_barang}
</p>
{/* ATRIBUT RELASI DATA */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 text-sm text-gray-600 dark:text-gray-300 pt-1">
<div className="flex items-center gap-1.5">
<Layers size={16} className="text-gray-400" />
<span>Kategori: <strong>{item.nama_kategori || "-"}</strong></span>
</div>
<div className="flex items-center gap-1.5">
<MapPin size={16} className="text-gray-400" />
<span>Lokasi: <strong>{item.nama_lokasi || "-"}</strong></span>
</div>
<div className="flex items-center gap-1.5">
<BadgeCheck size={16} className="text-green-500" />
<span>Penyerah: <strong>{item.nama_penyerah || "-"}</strong></span>
</div>
<div className="flex items-center gap-1.5">
<User size={16} className="text-blue-500" />
<span>Penerima: <strong>{item.nama_penerima || "-"}</strong></span>
</div>
</div>
<p className="text-xs text-gray-400">
Tanggal Transaksi: {item.dibuat_pada ? new Date(item.dibuat_pada).toLocaleString("id-ID") : "-"}
</p>
</div>
{/* SISI STATUS DOKUMEN DUAL APPROVAL */}
<div className="flex gap-2 self-stretch md:self-center justify-end border-t md:border-t-0 pt-3 md:pt-0 border-gray-100 dark:border-gray-700">
<span className={`text-xs font-bold px-3 py-1.5 rounded-full ${
item.status_serah === "Approved" ? "bg-green-100 text-green-700" : "bg-yellow-100 text-yellow-700"
}`}>
Serah: {item.status_serah}
</span>
<span className={`text-xs font-bold px-3 py-1.5 rounded-full ${
item.status_terima === "Approved" ? "bg-green-100 text-green-700" : "bg-yellow-100 text-yellow-700"
}`}>
Terima: {item.status_terima}
</span>
</div>
</div>
</div>
))}
</div>
) : (
<div className="p-12 text-center bg-white dark:bg-gray-800 rounded-3xl border border-gray-200 dark:border-gray-700 text-gray-400 font-medium">
Tidak ditemukan rekapan laporan transaksi barang, Bang! <FaBox />
</div>
)}
</div>
);
}