223 lines
8.4 KiB
TypeScript
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>
|
|
);
|
|
} |