0% found this document useful (0 votes)
6 views13 pages

Jurnal Kas TPQ

The document contains HTML and JavaScript code for a digital cash book application for Taman Pendidikan Al-Qur'an Al Qurabiy. It includes functionalities for adding transactions, generating reports in PDF format, and displaying daily cash journal data. The application utilizes Tailwind CSS for styling and SweetAlert for notifications, with a loader for data loading processes.

Uploaded by

psmmerakjaya
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views13 pages

Jurnal Kas TPQ

The document contains HTML and JavaScript code for a digital cash book application for Taman Pendidikan Al-Qur'an Al Qurabiy. It includes functionalities for adding transactions, generating reports in PDF format, and displaying daily cash journal data. The application utilizes Tailwind CSS for styling and SweetAlert for notifications, with a loader for data loading processes.

Uploaded by

psmmerakjaya
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 13

//Kode HTML

<!DOCTYPE html>
<html>
<head>
<base target="_top">
<title>Buku Kas TPQ Al Qurabiy</title>
<!-- Memuat Tailwind CSS untuk styling -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Memuat SweetAlert untuk notifikasi yang lebih baik -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
/* Custom CSS untuk spinner/loader */
.loader-spinner {
border: 8px solid #f3f3f3; /* Light grey */
border-top: 8px solid #16a34a; /* Green */
border-radius: 50%;
width: 60px;
height: 60px;
animation: spin 1s linear infinite;
}

@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body class="bg-gray-100 text-gray-800">

<div id="loader" class="hidden fixed inset-0 bg-gray-900 bg-opacity-50 flex


items-center justify-center z-50">
<div class="loader-spinner"></div>
</div>

<div class="container mx-auto p-4 md:p-8">

<!-- Header Aplikasi -->


<header class="text-center mb-8 p-6 bg-white rounded-lg shadow-md">
<h1 class="text-3xl font-bold text-green-700">Buku Kas Digital</h1>
<h2 class="text-xl text-gray-600">Taman Pendidikan Al-Qur'an Al
Qurabiy</h2>
<p class="text-sm text-gray-500 mt-1">Balongcino, Desa Blaru, Kecamatan
Badas, Kab. Kediri</p>
</header>

<main class="grid grid-cols-1 lg:grid-cols-3 gap-8">

<!-- Kolom Kiri: Form Input & Laporan -->


<div class="lg:col-span-1 space-y-6">

<!-- Form Tambah Transaksi -->


<div class="bg-white p-6 rounded-lg shadow-md">
<h3 class="text-lg font-semibold mb-4 border-b pb-2">Tambah
Transaksi Baru</h3>
<form id="kasForm" class="space-y-4">
<div>
<label for="tanggal" class="block text-sm font-medium
text-gray-700">Tanggal</label>
<input type="date" id="tanggal" name="tanggal" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-green-
500 focus:ring-green-500 sm:text-sm">
</div>
<div>
<label for="keterangan" class="block text-sm font-
medium text-gray-700">Keterangan</label>
<input type="text" id="keterangan" name="keterangan"
required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm
focus:border-green-500 focus:ring-green-500 sm:text-sm" placeholder="Contoh: Infaq
bulan Januari">
</div>
<div>
<label for="masuk" class="block text-sm font-medium
text-gray-700">Pemasukan (Debit)</label>
<input type="number" id="masuk" name="masuk" class="mt-
1 block w-full rounded-md border-gray-300 shadow-sm focus:border-green-500
focus:ring-green-500 sm:text-sm" placeholder="0">
</div>
<div>
<label for="keluar" class="block text-sm font-medium
text-gray-700">Pengeluaran (Kredit)</label>
<input type="number" id="keluar" name="keluar"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-green-
500 focus:ring-green-500 sm:text-sm" placeholder="0">
</div>
<button type="submit" class="w-full bg-green-600 hover:bg-
green-700 text-white font-bold py-2 px-4 rounded-lg shadow-md transition duration-
300">
Simpan Transaksi
</button>
</form>
</div>

<!-- Opsi Laporan -->


<div class="bg-white p-6 rounded-lg shadow-md">
<h3 class="text-lg font-semibold mb-4 border-b pb-2">Cetak
Laporan PDF</h3>

<div class="space-y-2 mb-4">


<label for="filterBulan" class="block text-sm font-medium
text-gray-700">Laporan Bulanan</label>
<div class="flex gap-2">
<input type="month" id="filterBulan" class="block w-
full rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring-green-
500 sm:text-sm">
<button id="btnCetakBulan" class="bg-blue-600
hover:bg-blue-700 text-white font-bold py-2 px-3 rounded-lg shadow-md transition
duration-300">
Cetak
</button>
</div>
</div>

<div class="space-y-2 mb-4">


<label class="block text-sm font-medium text-gray-
700">Laporan Sesuai Tanggal</label>
<div class="flex flex-col sm:flex-row gap-2">
<input type="date" id="startDate" class="block w-full
rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring-green-500
sm:text-sm">
<input type="date" id="endDate" class="block w-full
rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring-green-500
sm:text-sm">
</div>
<button id="btnCetakRange" class="mt-2 w-full bg-blue-600
hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-lg shadow-md transition
duration-300">
Cetak
</button>
</div>

<div class="space-y-2">
<label for="filterTahun" class="block text-sm font-medium
text-gray-700">Rekap Tahunan</label>
<div class="flex gap-2">
<input type="number" id="filterTahun" class="block w-
full rounded-md border-gray-300 shadow-sm focus:border-green-500 focus:ring-green-
500 sm:text-sm" placeholder="Contoh: 2024" min="2000" max="2100">
<button id="btnCetakTahun" class="bg-purple-600
hover:bg-purple-700 text-white font-bold py-2 px-3 rounded-lg shadow-md transition
duration-300">
Cetak
</button>
</div>
</div>

</div>

</div>

<!-- Kolom Kanan: Tabel Data Kas -->


<div class="lg:col-span-2 bg-white p-6 rounded-lg shadow-md">
<div class="flex justify-between items-center mb-4 border-b pb-2">
<h3 class="text-lg font-semibold">Jurnal Kas Harian</h3>
<button id="refreshBtn" class="bg-gray-200 hover:bg-gray-300
text-gray-800 font-bold py-2 px-4 rounded-lg transition duration-300">
Refresh Data
</button>
</div>
<div class="overflow-x-auto max-h-[600px]">
<table id="kasTable" class="min-w-full divide-y divide-gray-
200">
<thead class="bg-gray-50 sticky top-0">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium
text-gray-500 uppercase tracking-wider">Tanggal</th>
<th class="px-6 py-3 text-left text-xs font-medium
text-gray-500 uppercase tracking-wider">Keterangan</th>
<th class="px-6 py-3 text-left text-xs font-medium
text-gray-500 uppercase tracking-wider">Masuk</th>
<th class="px-6 py-3 text-left text-xs font-medium
text-gray-500 uppercase tracking-wider">Keluar</th>
<th class="px-6 py-3 text-left text-xs font-medium
text-gray-500 uppercase tracking-wider">Sisa Saldo</th>
</tr>
</thead>
<tbody id="kasTableBody" class="bg-white divide-y divide-
gray-200">
<tr><td colspan="5" class="text-center p-8">Memuat
data...</td></tr>
</tbody>
</table>
</div>
</div>

</main>

</div>

<script>
function showLoader()
{ document.getElementById('loader').classList.remove('hidden'); }
function hideLoader()
{ document.getElementById('loader').classList.add('hidden'); }

function loadKasData() {
showLoader();
google.script.run
.withSuccessHandler(data => {
const tableBody = document.getElementById('kasTableBody');
tableBody.innerHTML = '';
if (data && data.length > 0) {
data.reverse().forEach(row => {
const tr = document.createElement('tr');

let formattedDate = 'Tanggal tidak valid'; // Teks


default jika tanggal bermasalah
// Periksa apakah ada nilai tanggal (tidak null atau
undefined)
if (row && row[1]) {
const dateObj = new Date(row[1]);
// Periksa apakah objek tanggal yang dibuat valid
sebelum diformat
if (!isNaN(dateObj.getTime())) {
formattedDate = dateObj.toLocaleDateString('id-
ID', { day: '2-digit', month: 'short', year: 'numeric' });
}
}

tr.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap text-sm
text-gray-900">${formattedDate}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm
text-gray-900">${row[2]}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm
text-green-600 text-right">${formatRupiah(row[3])}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm
text-red-600 text-right">${formatRupiah(row[4])}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm
text-gray-900 font-medium text-right">${formatRupiah(row[5])}</td>
`;
tableBody.appendChild(tr);
});
} else {
tableBody.innerHTML = '<tr><td colspan="5" class="text-
center p-8 text-gray-500">Belum ada data transaksi.</td></tr>';
}
hideLoader();
})
.withFailureHandler(err => {
Swal.fire('Error Memuat Data', err.message, 'error');
hideLoader();
})
.getKasData();
}

function formatRupiah(angka) {
return new Intl.NumberFormat('id-ID', { style: 'currency', currency:
'IDR', minimumFractionDigits: 0 }).format(angka);
}

function downloadPdf(base64Data, fileName) {


const byteCharacters = atob(base64Data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: 'application/pdf' });

const link = document.createElement('a');


link.href = URL.createObjectURL(blob);
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}

document.addEventListener('DOMContentLoaded', () => {
if (typeof google === 'undefined' || !google.script.run) {
hideLoader();
Swal.fire({
icon: 'error',
title: 'Koneksi Gagal',
text: 'Aplikasi tidak dapat terhubung ke server. Pastikan Anda
membuka dari URL Web App yang benar.',
footer: 'Kesalahan: "google.script.run" tidak ditemukan.'
});
document.querySelectorAll('button, input').forEach(el =>
el.disabled = true);
document.getElementById('kasTableBody').innerHTML = '<tr><td
colspan="5" class="text-center p-8 text-red-500">Koneksi ke server
gagal.</td></tr>';
return;
}

document.getElementById('tanggal').valueAsDate = new Date();


document.getElementById('filterTahun').value = new
Date().getFullYear();
loadKasData();

document.getElementById('kasForm').addEventListener('submit',
function(e) {
e.preventDefault();
showLoader();
const formData = {
tanggal: this.tanggal.value,
keterangan: this.keterangan.value,
masuk: this.masuk.value,
keluar: this.keluar.value
};
google.script.run
.withSuccessHandler(response => {
Swal.fire('Sukses!', response, 'success');
this.reset();
document.getElementById('tanggal').valueAsDate = new
Date();
loadKasData();
})
.withFailureHandler(err => {
Swal.fire('Gagal Menyimpan', err.message, 'error');
hideLoader();
})
.addKasData(formData);
});

document.getElementById('refreshBtn').addEventListener('click',
loadKasData);

document.getElementById('btnCetakBulan').addEventListener('click', ()
=> {
const monthInput = document.getElementById('filterBulan').value;
if (!monthInput) {
Swal.fire('Perhatian', 'Silakan pilih bulan terlebih dahulu.',
'warning');
return;
}
const [year, month] = monthInput.split('-');
const startDate = new Date(year, month - 1, 1);
const endDate = new Date(year, month, 0);
const reportTitle = `Bulan ${startDate.toLocaleString('id-ID',
{ month: 'long', year: 'numeric' })}`;

showLoader();
google.script.run
.withSuccessHandler(result => {
downloadPdf(result.data, result.fileName);
hideLoader();
})
.withFailureHandler(err => { Swal.fire('Gagal Cetak PDF',
err.message, 'error'); hideLoader(); })
.createPdfReport(startDate.toISOString().split('T')[0],
endDate.toISOString().split('T')[0], reportTitle);
});

document.getElementById('btnCetakRange').addEventListener('click', ()
=> {
const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value;
if (!startDate || !endDate) {
Swal.fire('Perhatian', 'Silakan isi tanggal mulai dan tanggal
akhir.', 'warning');
return;
}
if (new Date(startDate) > new Date(endDate)) {
Swal.fire('Perhatian', 'Tanggal mulai tidak boleh lebih besar
dari tanggal akhir.', 'warning');
return;
}
const formattedStart = new Date(startDate).toLocaleDateString('id-
ID', { day: '2-digit', month: 'long', year: 'numeric' });
const formattedEnd = new Date(endDate).toLocaleDateString('id-ID',
{ day: '2-digit', month: 'long', year: 'numeric' });
const reportTitle = `Periode ${formattedStart} s/d $
{formattedEnd}`;

showLoader();
google.script.run
.withSuccessHandler(result => {
downloadPdf(result.data, result.fileName);
hideLoader();
})
.withFailureHandler(err => { Swal.fire('Gagal Cetak PDF',
err.message, 'error'); hideLoader(); })
.createPdfReport(startDate, endDate, reportTitle);
});

document.getElementById('btnCetakTahun').addEventListener('click', ()
=> {
const year = document.getElementById('filterTahun').value;
if (!year) {
Swal.fire('Perhatian', 'Silakan masukkan tahun terlebih
dahulu.', 'warning');
return;
}
showLoader();
google.script.run
.withSuccessHandler(result => {
downloadPdf(result.data, result.fileName);
hideLoader();
})
.withFailureHandler(err => { Swal.fire('Gagal Cetak PDF',
err.message, 'error'); hideLoader(); })
.createYearlyRecap(year);
});
});
</script>
</body>
</html>

//KODE GS
// ===============================================================
// KONFIGURASI GOOGLE SHEET
// ===============================================================
const SHEET_ID = '1vBZY6VuhIhD_z-aZ05Dq4bXw8EgXRKI1CTNAPLiacZ8';
const SHEET_NAME = 'Kas';

// ===============================================================
// FUNGSI UTAMA UNTUK MENAMPILKAN WEB APP
// ===============================================================
function doGet() {
return HtmlService.createHtmlOutputFromFile('index')
.setTitle('Buku Kas TPQ Al Qurabiy')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.DEFAULT);
}

// ===============================================================
// FUNGSI UNTUK MENGAMBIL SEMUA DATA KAS
// ===============================================================
function getKasData() {
try {
const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
const dataRange = sheet.getDataRange();
const values = dataRange.getValues().slice(1); // Ambil semua data kecuali
header

// **PERBAIKAN KRUSIAL:**
// Memastikan semua tanggal dikonversi ke format standar (ISO String)
// sebelum dikirim ke aplikasi web. Ini menangani kasus di mana
// Google Sheet mungkin mengirim tanggal sebagai string atau objek Date.
return values.map(row => {
const originalDate = row[1]; // Kolom Tanggal ada di indeks 1
if (originalDate) {
// Coba buat objek Date yang valid dari nilai apa pun di sel
const dateObj = new Date(originalDate);
// Jika berhasil (bukan tanggal yang tidak valid), ubah ke format ISO
lengkap
if (dateObj && !isNaN(dateObj.getTime())) {
row[1] = dateObj.toISOString();
} else {
// Jika tidak bisa di-parse, kirim null agar tidak menyebabkan error di
aplikasi web
row[1] = null;
}
}
return row;
});
} catch (e) {
throw new Error('Gagal mengambil data dari Google Sheet. Pastikan ID Sheet dan
nama Sheet sudah benar. Error: ' + e.message);
}
}

// ===============================================================
// FUNGSI UNTUK MENAMBAHKAN DATA BARU
// ===============================================================
function addKasData(formData) {
try {
const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
const lastRow = sheet.getLastRow();

let saldoSebelumnya = 0;
if (lastRow > 1) {
saldoSebelumnya = parseFloat(sheet.getRange(lastRow, 6).getValue()) || 0;
}

const masuk = parseFloat(formData.masuk) || 0;


const keluar = parseFloat(formData.keluar) || 0;
const sisaSaldo = saldoSebelumnya + masuk - keluar;
const newRow = [
new Date(), // Timestamp (Kolom A)
new Date(formData.tanggal), // Tanggal (Kolom B)
formData.keterangan, // Keterangan (Kolom C)
masuk, // Masuk (Kolom D)
keluar, // Keluar (Kolom E)
sisaSaldo // Sisa (Kolom F)
];

sheet.appendRow(newRow);

return 'Transaksi berhasil disimpan!';


} catch (e) {
throw new Error('Gagal menyimpan data ke Google Sheet. Error: ' + e.message);
}
}

// ===============================================================
// FUNGSI UNTUK MEMBUAT LAPORAN PDF (BULANAN/RANGE)
// ===============================================================
function createPdfReport(startDate, endDate, reportTitle) {
const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
const allData = sheet.getDataRange().getValues().slice(1);

const start = new Date(startDate);


start.setHours(0, 0, 0, 0);
const end = new Date(endDate);
end.setHours(23, 59, 59, 999);

const filteredData = allData.filter(row => {


if (!row[1] || isNaN(new Date(row[1]).getTime())) return false; // Lewati baris
dengan tanggal tidak valid
const rowDate = new Date(row[1]);
return rowDate >= start && rowDate <= end;
});

if (filteredData.length === 0) {
throw new Error('Tidak ada data transaksi pada periode yang dipilih.');
}

let saldoAwal = 0;
// Mencari saldo awal dari baris sebelum transaksi pertama yang difilter
const firstTransactionTimestamp = filteredData[0][0];
const firstTransactionIndexInAllData = allData.findIndex(row => new
Date(row[0]).getTime() === new Date(firstTransactionTimestamp).getTime());

if (firstTransactionIndexInAllData > 0) {
saldoAwal = parseFloat(allData[firstTransactionIndexInAllData - 1][5]) || 0;
}

let totalMasuk = 0;
let totalKeluar = 0;

let rowsHtml = filteredData.map(row => {


const tanggal = new Date(row[1]).toLocaleDateString('id-ID');
const masuk = parseFloat(row[3] || 0);
const keluar = parseFloat(row[4] || 0);
totalMasuk += masuk;
totalKeluar += keluar;
return `
<tr>
<td>${tanggal}</td>
<td>${row[2]}</td>
<td class="currency">${formatRupiah(masuk)}</td>
<td class="currency">${formatRupiah(keluar)}</td>
</tr>
`;
}).join('');

const saldoAkhir = saldoAwal + totalMasuk - totalKeluar;

const htmlContent = `
<html>
<head>
<style>
body { font-family: 'Helvetica', 'Arial', sans-serif; font-size: 10pt;
color: #333; }
.header { text-align: center; margin-bottom: 20px; border-bottom: 2px
solid #000; padding-bottom: 10px; }
.header h1 { margin: 0; font-size: 16pt; }
.header h2 { margin: 0; font-size: 14pt; font-weight: normal; }
.header p { margin: 5px 0 0; font-size: 9pt; }
.report-title { text-align: center; margin-bottom: 20px; font-size: 12pt;
font-weight: bold; text-decoration: underline;}
table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
th, td { border: 1px solid #999; padding: 6px; text-align: left; }
th { background-color: #f2f2f2; font-weight: bold; }
.summary-table { width: 50%; margin-left: 50%; }
.summary-table td { border: none; }
.currency { text-align: right; }
.footer { margin-top: 40px; font-size: 10pt; }
.signatures { margin-top: 60px; width: 100%; text-align: center; }
.signature-box { display: inline-block; width: 45%; }
</style>
</head>
<body>
<div class="header">
<h1>BUKU KAS</h1>
<h2>TAMAN PENDIDIKAN AL-QUR'AN AL QURABIY</h2>
<p>Balongcino, Desa Blaru, Kecamatan Badas, Kab. Kediri</p>
</div>
<div class="report-title">LAPORAN KAS ${reportTitle.toUpperCase()}</div>
<table>
<thead>
<tr>
<th>Tanggal</th>
<th>Keterangan</th>
<th>Pemasukan</th>
<th>Pengeluaran</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2"><b>Saldo Awal</b></td>
<td class="currency"
colspan="2"><b>${formatRupiah(saldoAwal)}</b></td>
</tr>
${rowsHtml}
</tbody>
</table>
<table class="summary-table">
<tr><td>Total Pemasukan</td><td class="currency">$
{formatRupiah(totalMasuk)}</td></tr>
<tr><td>Total Pengeluaran</td><td class="currency">$
{formatRupiah(totalKeluar)}</td></tr>
<tr><td><b>Saldo Akhir</b></td><td class="currency"><b>$
{formatRupiah(saldoAkhir)}</b></td></tr>
</table>
<div class="footer">
<p>Balongcino, ${new Date().toLocaleDateString('id-ID', {day:'2-digit',
month:'long', year:'numeric'})}</p>
<div class="signatures">
<div class="signature-box">
<p>Bendahara,</p>
<br><br><br>
<p>(___________________)</p>
</div>
<div class="signature-box">
<p>Ketua,</p>
<br><br><br>
<p>(___________________)</p>
</div>
</div>
</div>
</body>
</html>
`;

const blob = Utilities.newBlob(htmlContent, MimeType.HTML).getAs(MimeType.PDF);


const fileName = `Laporan_Kas_${reportTitle.replace(/ /g, '_')}.pdf`;
return {
data: Utilities.base64Encode(blob.getBytes()),
fileName: fileName
};
}

// ===============================================================
// FUNGSI UNTUK MEMBUAT REKAP TAHUNAN
// ===============================================================
function createYearlyRecap(year) {
const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
const allData = sheet.getDataRange().getValues().slice(1);

const filteredData = allData.filter(row => {


if (!row[1] || isNaN(new Date(row[1]).getTime())) return false;
return new Date(row[1]).getFullYear() == year
});

if (filteredData.length === 0) {
throw new Error(`Tidak ada data transaksi pada tahun ${year}.`);
}

const monthlyRecap = Array(12).fill(0).map(() => ({ masuk: 0, keluar: 0 }));

filteredData.forEach(row => {
const month = new Date(row[1]).getMonth(); // 0 = Januari, 11 = Desember
monthlyRecap[month].masuk += parseFloat(row[3] || 0);
monthlyRecap[month].keluar += parseFloat(row[4] || 0);
});

let totalMasukTahunan = 0;
let totalKeluarTahunan = 0;

const monthNames = ["Januari", "Februari", "Maret", "April", "Mei", "Juni",


"Juli", "Agustus", "September", "Oktober", "November", "Desember"];

let rowsHtml = monthlyRecap.map((recap, index) => {


totalMasukTahunan += recap.masuk;
totalKeluarTahunan += recap.keluar;
return `
<tr>
<td>${monthNames[index]}</td>
<td class="currency">${formatRupiah(recap.masuk)}</td>
<td class="currency">${formatRupiah(recap.keluar)}</td>
</tr>
`;
}).join('');

const htmlContent = `
<html>
<head>
<style>
body { font-family: 'Helvetica', 'Arial', sans-serif; font-size: 10pt;
color: #333; }
.header { text-align: center; margin-bottom: 20px; border-bottom: 2px
solid #000; padding-bottom: 10px; }
.header h1 { margin: 0; font-size: 16pt; }
.header h2 { margin: 0; font-size: 14pt; font-weight: normal; }
.header p { margin: 5px 0 0; font-size: 9pt; }
.report-title { text-align: center; margin-bottom: 20px; font-size: 12pt;
font-weight: bold; text-decoration: underline;}
table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
th, td { border: 1px solid #999; padding: 6px; text-align: left; }
th { background-color: #f2f2f2; font-weight: bold; }
.currency { text-align: right; }
.footer { margin-top: 40px; font-size: 10pt; }
.signatures { margin-top: 60px; width: 100%; text-align: center; }
.signature-box { display: inline-block; width: 45%; }
</style>
</head>
<body>
<div class="header">
<h1>REKAP KAS TAHUNAN</h1>
<h2>TAMAN PENDIDIKAN AL-QUR'AN AL QURABIY</h2>
<p>Balongcino, Desa Blaru, Kecamatan Badas, Kab. Kediri</p>
</div>
<div class="report-title">TAHUN ${year}</div>
<table>
<thead>
<tr>
<th>Bulan</th>
<th>Total Pemasukan</th>
<th>Total Pengeluaran</th>
</tr>
</thead>
<tbody>
${rowsHtml}
<tr>
<td><b>TOTAL</b></td>
<td class="currency"><b>${formatRupiah(totalMasukTahunan)}</b></td>
<td class="currency"><b>${formatRupiah(totalKeluarTahunan)}</b></td>
</tr>
</tbody>
</table>
<div class="footer">
<p>Balongcino, ${new Date().toLocaleDateString('id-ID', {day:'2-digit',
month:'long', year:'numeric'})}</p>
<div class="signatures">
<div class="signature-box">
<p>Bendahara,</p>
<br><br><br>
<p>(___________________)</p>
</div>
<div class="signature-box">
<p>Ketua,</p>
<br><br><br>
<p>(___________________)</p>
</div>
</div>
</div>
</body>
</html>
`;

const blob = Utilities.newBlob(htmlContent, MimeType.HTML).getAs(MimeType.PDF);


const fileName = `Rekap_Kas_Tahunan_${year}.pdf`;
return {
data: Utilities.base64Encode(blob.getBytes()),
fileName: fileName
};
}

// ===============================================================
// FUNGSI BANTUAN (HELPER)
// ===============================================================
function formatRupiah(angka) {
if (angka === null || angka === undefined || isNaN(parseFloat(angka))) {
return "Rp 0";
}
return "Rp " + parseFloat(angka).toLocaleString('id-ID');
}

You might also like