first commit

This commit is contained in:
akbarguci.pkl 2026-05-19 08:41:03 +07:00
parent 4518db6940
commit e8f3995cea
1 changed files with 261 additions and 58 deletions

View File

@ -29,10 +29,11 @@ export default function TransaksiBAST() {
======================================================= */ ======================================================= */
useEffect(() => { useEffect(() => {
setLoading(true); setLoading(true);
axios axios
.get("http://localhost:5000/api/bast") .get("http://localhost:5000/api/bast")
.then((res) => { .then((res) => {
// PENGAMAN KREDENSIAL: Memastikan data yang masuk ke state murni bertipe Array
if (res.data && res.data.success && Array.isArray(res.data.data)) { if (res.data && res.data.success && Array.isArray(res.data.data)) {
setDataBAST(res.data.data); setDataBAST(res.data.data);
} else if (Array.isArray(res.data)) { } else if (Array.isArray(res.data)) {
@ -51,23 +52,30 @@ export default function TransaksiBAST() {
}, []); }, []);
/* ======================================================= /* =======================================================
DOWNLOAD PDF (Sinkronisasi Properti Sesuai Database) PREVIEW PDF
======================================================= */ ======================================================= */
const downloadPDF = async (item: any) => { const downloadPDF = async (item: any) => {
const doc = new jsPDF(); const doc = new jsPDF();
/* ========================= /* =========================
QR CODE EXTRACTION QR CODE EXTRACTION
========================= */ ========================= */
let qrImage = ""; let qrImage = "";
try { try {
// Menggunakan fallback jika qr_image sudah ada di dalam object barang
const qrRes = await axios.get( const qrRes = await axios.get(
`http://localhost:5000/api/qrcode/${item.bast_id}` `http://localhost:5000/api/qrcode/${item.bast_id}`
); );
qrImage = qrRes.data.qr; qrImage = qrRes.data.qr;
} catch (err) { } catch (err) {
console.log("QR Code endpoint not found, menggunakan QR default jika tersedia.");
console.log(
"QR Code endpoint not found, menggunakan QR default jika tersedia."
);
} }
if (qrImage) { if (qrImage) {
@ -78,7 +86,9 @@ export default function TransaksiBAST() {
KODE DOKUMEN BAST KODE DOKUMEN BAST
========================= */ ========================= */
doc.rect(140, 50, 50, 10); doc.rect(140, 50, 50, 10);
doc.setFontSize(10); doc.setFontSize(10);
doc.text(`BAST-#${item.bast_id}`, 143, 57); doc.text(`BAST-#${item.bast_id}`, 143, 57);
/* ========================= /* =========================
@ -86,8 +96,14 @@ export default function TransaksiBAST() {
========================= */ ========================= */
doc.setFont("helvetica", "bold"); doc.setFont("helvetica", "bold");
doc.setFontSize(15); doc.setFontSize(15);
doc.text("BERITA ACARA", 105, 80, { align: "center" });
doc.text("SERAH TERIMA BARANG/ASSET", 105, 92, { align: "center" }); doc.text("BERITA ACARA", 105, 80, {
align: "center",
});
doc.text("SERAH TERIMA BARANG/ASSET", 105, 92, {
align: "center",
});
/* ========================= /* =========================
ISI SURAT KONTRAK BAST ISI SURAT KONTRAK BAST
@ -95,25 +111,55 @@ export default function TransaksiBAST() {
doc.setFont("helvetica", "normal"); doc.setFont("helvetica", "normal");
doc.setFontSize(11); doc.setFontSize(11);
// Konversi tanggal sql ke format readable lokal jika ada const waktuBuat = item.dibuat_pada
const waktuBuat = item.dibuat_pada ? new Date(item.dibuat_pada).toLocaleDateString("id-ID") : "-"; ? new Date(item.dibuat_pada).toLocaleDateString("id-ID")
: "-";
doc.text(`Pada hari ini, tanggal ${waktuBuat},`, 20, 115); doc.text(
doc.text(`telah dilakukan serah terima barang secara sah antara pihak:`, 20, 128); `Pada hari ini, tanggal ${waktuBuat},`,
20,
115
);
doc.text(
`telah dilakukan serah terima barang secara sah antara pihak:`,
20,
128
);
doc.setFont("helvetica", "bold"); doc.setFont("helvetica", "bold");
doc.text(`[Penyerah] ${item.nama_penyerah || "-"}`, 25, 138);
doc.text(`[Penerima] ${item.nama_penerima || "-"}`, 25, 146); doc.text(
`[Penyerah] ${item.nama_penyerah || "-"}`,
25,
138
);
doc.text(
`[Penerima] ${item.nama_penerima || "-"}`,
25,
146
);
doc.setFont("helvetica", "normal"); doc.setFont("helvetica", "normal");
doc.text("Adapun rincian barang/aset CIFO yang diserahterimakan sebagai berikut:", 20, 160);
doc.text(
"Adapun rincian barang/aset CIFO yang diserahterimakan sebagai berikut:",
20,
160
);
/* ========================= /* =========================
TABEL RINCIAN ASET UKK TABEL RINCIAN ASET
========================= */ ========================= */
autoTable(doc, { autoTable(doc, {
startY: 168, startY: 168,
margin: { left: 20, right: 20 },
margin: {
left: 20,
right: 20,
},
styles: { styles: {
fontSize: 10, fontSize: 10,
halign: "center", halign: "center",
@ -121,15 +167,25 @@ export default function TransaksiBAST() {
lineWidth: 0.3, lineWidth: 0.3,
lineColor: [0, 0, 0], lineColor: [0, 0, 0],
}, },
headStyles: { headStyles: {
fillColor: [255, 255, 255], fillColor: [255, 255, 255],
textColor: [0, 0, 0], textColor: [0, 0, 0],
fontStyle: "bold", fontStyle: "bold",
}, },
bodyStyles: { bodyStyles: {
textColor: [0, 0, 0], textColor: [0, 0, 0],
}, },
head: [["No.", "Kode Barang", "Nama Barang", "Kondisi Serah", "Kondisi Terima"]],
head: [[
"No.",
"Kode Barang",
"Nama Barang",
"Kondisi Serah",
"Kondisi Terima"
]],
body: [[ body: [[
"1", "1",
item.kode_barang || "-", item.kode_barang || "-",
@ -140,145 +196,292 @@ export default function TransaksiBAST() {
}); });
/* ========================= /* =========================
DOKUMEN KLAUSUL PENUTUP DOKUMEN PENUTUP
========================= */ ========================= */
doc.setFont("helvetica", "normal"); doc.setFont("helvetica", "normal");
doc.setFontSize(11); doc.setFontSize(11);
doc.text("Demikian Berita Acara Serah Terima ini dibuat dengan sebenar-benarnya dan", 20, 220);
doc.text("dipergunakan sebagaimana mestinya untuk keperluan inventarisasi sekolah.", 20, 232); doc.text(
"Demikian Berita Acara Serah Terima ini dibuat dengan sebenar-benarnya dan",
20,
220
);
doc.text(
"dipergunakan sebagaimana mestinya untuk keperluan inventarisasi sekolah.",
20,
232
);
/* ========================= /* =========================
TANDA TANGAN (TTD) TANDA TANGAN
========================= */ ========================= */
doc.setFont("helvetica", "bold"); doc.setFont("helvetica", "bold");
doc.text("Yang Menyerahkan,", 20, 255);
doc.text("Yang Menerima,", 125, 255); doc.text(
"Yang Menyerahkan,",
20,
255
);
doc.text(
"Yang Menerima,",
125,
255
);
doc.setFont("helvetica", "normal"); doc.setFont("helvetica", "normal");
doc.text(item.nama_penyerah || "-", 20, 285);
doc.text(item.nama_penerima || "-", 125, 285); doc.text(
item.nama_penyerah || "-",
20,
285
);
doc.text(
item.nama_penerima || "-",
125,
285
);
/* ========================= /* =========================
SAVE EKSPOR FILE PDF PREVIEW PDF DI TAB BARU
========================= */ ========================= */
doc.save(`BAST_CIFO_${item.bast_id}.pdf`); const pdfBlob = doc.output("blob");
const pdfUrl = URL.createObjectURL(pdfBlob);
window.open(pdfUrl, "_blank");
}; };
return ( return (
<div className="p-8 min-h-screen bg-gray-100 dark:bg-gray-900 transition-all duration-300"> <div className="p-8 min-h-screen bg-gray-100 dark:bg-gray-900 transition-all duration-300">
{/* HEADER UTAMA */} {/* HEADER */}
<div className="flex justify-between items-center mb-8"> <div className="flex justify-between items-center mb-8">
<div> <div>
<h1 className="text-4xl font-bold dark:text-white"> <h1 className="text-4xl font-bold dark:text-white">
BAST BAST
</h1> </h1>
<p className="text-gray-500 dark:text-gray-300 mt-2"> <p className="text-gray-500 dark:text-gray-300 mt-2">
Data serah terima barang logistik resmi perusahaan & sekolah. Data serah terima barang logistik resmi perusahaan & sekolah.
</p> </p>
</div> </div>
</div> </div>
{/* RENDER LAYOUT KONDISIONAL */} {/* RENDER DATA */}
{loading ? ( {loading ? (
<div className="p-12 text-center bg-white dark:bg-gray-800 rounded-3xl text-blue-600 font-medium shadow-md animate-pulse"> <div className="p-12 text-center bg-white dark:bg-gray-800 rounded-3xl text-blue-600 font-medium shadow-md animate-pulse">
Sedang menyinkronkan data dokumen BAST dari server CIFO... ⏳ Sedang menyinkronkan data dokumen BAST dari server CIFO... ⏳
</div> </div>
) : dataBAST && dataBAST.length > 0 ? ( ) : dataBAST && dataBAST.length > 0 ? (
<div className="space-y-5"> <div className="space-y-5">
{dataBAST.map((item) => ( {dataBAST.map((item) => (
<div <div
key={item.bast_id} key={item.bast_id}
className="bg-white dark:bg-gray-800 rounded-3xl shadow-md p-6 border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-all duration-300" className="bg-white dark:bg-gray-800 rounded-3xl shadow-md p-6 border border-gray-200 dark:border-gray-700 hover:shadow-lg transition-all duration-300"
> >
<div className="flex flex-col sm:flex-row justify-between items-start gap-4"> <div className="flex flex-col sm:flex-row justify-between items-start gap-4">
<div className="space-y-2.5 flex-1"> <div className="space-y-2.5 flex-1">
{/* ID BAST */}
{/* ID */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<FileText size={20} className="text-blue-600 shrink-0" />
<FileText
size={20}
className="text-blue-600 shrink-0"
/>
<h2 className="text-xl font-extrabold dark:text-white"> <h2 className="text-xl font-extrabold dark:text-white">
Dokumen BAST #{item.bast_id} Dokumen BAST #{item.bast_id}
</h2> </h2>
</div> </div>
{/* INFO UTAMA BARANG */} {/* BARANG */}
<div> <div>
<span className="text-xs font-mono font-bold px-2 py-0.5 bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded"> <span className="text-xs font-mono font-bold px-2 py-0.5 bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded">
{item.kode_barang || "KODE-BLANK"} {item.kode_barang || "KODE-BLANK"}
</span> </span>
<p className="text-xl font-bold text-blue-600 dark:text-blue-400 mt-1"> <p className="text-xl font-bold text-blue-600 dark:text-blue-400 mt-1">
{item.nama_barang} {item.nama_barang}
</p> </p>
</div> </div>
{/* KATEGORI & LOKASI ATRIBUT */} {/* KATEGORI */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-1.5 pt-1 text-sm text-gray-600 dark:text-gray-300"> <div className="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-1.5 pt-1 text-sm text-gray-600 dark:text-gray-300">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Layers size={16} className="text-gray-400" />
<span>Kategori: <strong className="text-gray-800 dark:text-gray-100">{item.nama_kategori || "-"}</strong></span> <Layers
size={16}
className="text-gray-400"
/>
<span>
Kategori:
<strong className="text-gray-800 dark:text-gray-100">
{" "}
{item.nama_kategori || "-"}
</strong>
</span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<MapPin size={16} className="text-gray-400" />
<span>Lokasi: <strong className="text-gray-800 dark:text-gray-100">{item.nama_lokasi || "-"}</strong></span> <MapPin
size={16}
className="text-gray-400"
/>
<span>
Lokasi:
<strong className="text-gray-800 dark:text-gray-100">
{" "}
{item.nama_lokasi || "-"}
</strong>
</span>
</div> </div>
</div> </div>
{/* PETUGAS OTORISASI (PENYERAH & PENERIMA) */} {/* USER */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-1.5 pt-1 border-t border-gray-100 dark:border-gray-700 text-sm text-gray-600 dark:text-gray-300"> <div className="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-1.5 pt-1 border-t border-gray-100 dark:border-gray-700 text-sm text-gray-600 dark:text-gray-300">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<BadgeCheck size={16} className="text-green-500" />
<span>Penyerah: <strong className="text-gray-800 dark:text-gray-100">{item.nama_penyerah || "-"}</strong></span> <BadgeCheck
size={16}
className="text-green-500"
/>
<span>
Penyerah:
<strong className="text-gray-800 dark:text-gray-100">
{" "}
{item.nama_penyerah || "-"}
</strong>
</span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<User size={16} className="text-blue-500" />
<span>Penerima: <strong className="text-gray-800 dark:text-gray-100">{item.nama_penerima || "-"}</strong></span> <User
size={16}
className="text-blue-500"
/>
<span>
Penerima:
<strong className="text-gray-800 dark:text-gray-100">
{" "}
{item.nama_penerima || "-"}
</strong>
</span>
</div> </div>
</div> </div>
{/* TRACKING REALTIME TIME STAMP */} {/* TANGGAL */}
<div className="flex items-center gap-2 text-xs text-gray-400 pt-1"> <div className="flex items-center gap-2 text-xs text-gray-400 pt-1">
<Clock3 size={14} /> <Clock3 size={14} />
<span>Dibuat Pada: {item.dibuat_pada ? new Date(item.dibuat_pada).toLocaleString("id-ID") : "-"}</span>
<span>
Dibuat Pada:{" "}
{item.dibuat_pada
? new Date(item.dibuat_pada).toLocaleString("id-ID")
: "-"}
</span>
</div> </div>
</div> </div>
{/* SISI KANAN CONTROL STATUS & CETAK PDF */} {/* KANAN */}
<div className="flex sm:flex-col items-end justify-between sm:justify-start w-full sm:w-auto gap-3 self-stretch sm:self-start border-t sm:border-t-0 pt-4 sm:pt-0 border-gray-100 dark:border-gray-700"> <div className="flex sm:flex-col items-end justify-between sm:justify-start w-full sm:w-auto gap-3 self-stretch sm:self-start border-t sm:border-t-0 pt-4 sm:pt-0 border-gray-100 dark:border-gray-700">
<div className="flex flex-col gap-1 items-end"> <div className="flex flex-col gap-1 items-end">
<span className="text-[10px] uppercase font-bold text-gray-400 tracking-wider">Status Dokumen</span>
<span className="text-[10px] uppercase font-bold text-gray-400 tracking-wider">
Status Dokumen
</span>
<div className="flex gap-1.5"> <div className="flex gap-1.5">
<span className={`text-xs font-bold px-3 py-1 rounded-full ${
item.status_serah === "Approved" ? "bg-green-100 text-green-700" : "bg-yellow-100 text-yellow-700" <span
}`}> className={`text-xs font-bold px-3 py-1 rounded-full ${
item.status_serah === "Approved"
? "bg-green-100 text-green-700"
: "bg-yellow-100 text-yellow-700"
}`}
>
Serah: {item.status_serah} Serah: {item.status_serah}
</span> </span>
<span className={`text-xs font-bold px-3 py-1 rounded-full ${
item.status_terima === "Approved" ? "bg-green-100 text-green-700" : "bg-yellow-100 text-yellow-700" <span
}`}> className={`text-xs font-bold px-3 py-1 rounded-full ${
item.status_terima === "Approved"
? "bg-green-100 text-green-700"
: "bg-yellow-100 text-yellow-700"
}`}
>
Terima: {item.status_terima} Terima: {item.status_terima}
</span> </span>
</div> </div>
</div> </div>
<button <button
onClick={() => downloadPDF(item)} onClick={() => downloadPDF(item)}
className="flex items-center gap-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold text-sm px-4 py-2.5 rounded-xl shadow-md hover:shadow-lg transition duration-200" className="flex items-center gap-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold text-sm px-4 py-2.5 rounded-xl shadow-md hover:shadow-lg transition duration-200"
> >
<Download size={16} /> <Download size={16} />
Cetak PDF
Preview PDF
</button> </button>
</div> </div>
</div> </div>
</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"> <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">
Belum ada riwayat dokumen transaksi BAST terdaftar, Bang! 📦 Belum ada riwayat dokumen transaksi BAST terdaftar, Bang! 📦
</div> </div>
)} )}
</div> </div>
); );
} }