🚀 Panduan Instalasi GAS WebApp – Jurnal Anak Hebat (Update)
💡 Developed by @Suyanto
Pembaruan Fitur:
- Daftar Nama: Nama anak kini bisa dipilih dari database (Sheet
DataAnak). - Ceklis Semua: Tombol cepat untuk mencentang semua kebiasaan.
- Cetak PDF: Fitur untuk mencetak riwayat jurnal ke format PDF.
Langkah Instalasi:
- Salin kode
Code.gsdanIndex.htmlterbaru ke editor Google Apps Script Anda. - WAJIB: Jalankan fungsi
setupDatabasekembali di editor Apps Script untuk membuat SheetDataAnak. - Buka Google Sheet Anda, cari sheet
DataAnak, dan isi kolom Nama serta Kelas di sana sebagai database pilihan. - Simpan (Ctrl+S) dan Deploy ulang (New Deployment) jika sudah pernah dideploy sebelumnya
Code.Gs
// Generated by GAS WebApp Builder | Created by @farhanalfaizi
function doGet() {
return HtmlService.createTemplateFromFile(‘Index’)
.evaluate()
.setTitle(‘Jurnal Kolektif Anak Hebat’)
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
.addMetaTag(‘viewport’, ‘width=device-width, initial-scale=1’);
}
/**
* Sinkronisasi/Setup Database
*/
function setupDatabase() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheetName = ‘JurnalKebiasaan’;
let sheet = ss.getSheetByName(sheetName);
if (!sheet) {
sheet = ss.insertSheet(sheetName);
const headers = [‘ID’, ‘Tanggal Input’, ‘Nama Anak’, ‘Kelas’, ‘Bangun Pagi’, ‘Beribadah’, ‘Berolahraga’, ‘Makan Sehat’, ‘Gemar Belajar’, ‘Bermasyarakat’, ‘Tidur Cepat’, ‘Timestamp’];
sheet.getRange(1, 1, 1, headers.length).setValues([headers]).setBackground(‘#4F46E5’).setFontColor(‘#FFFFFF’).setFontWeight(‘bold’);
}
return “Database Berhasil Disiapkan!”;
}
function getDaftarAnak() {
try {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(‘DataAnak’);
if (!sheet) return { success: false, message: “Sheet ‘DataAnak’ tidak ditemukan” };
const data = sheet.getDataRange().getValues();
data.shift();
return { success: true, data: data };
} catch (e) {
return { success: false, message: e.toString() };
}
}
function simpanJurnalKolektif(dataArray, tanggalInput) {
try {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(‘JurnalKebiasaan’);
const timestamp = new Date();
dataArray.forEach(data => {
const id = “ID-” + new Date().getTime() + “-” + Math.floor(Math.random() * 1000);
sheet.appendRow([
id, tanggalInput, data.nama, data.kelas,
data.habits[0] ? “✅” : “❌”, data.habits[1] ? “✅” : “❌”,
data.habits[2] ? “✅” : “❌”, data.habits[3] ? “✅” : “❌”,
data.habits[4] ? “✅” : “❌”, data.habits[5] ? “✅” : “❌”,
data.habits[6] ? “✅” : “❌”, timestamp
]);
});
return { success: true, message: dataArray.length + ” data berhasil disimpan.” };
} catch (e) {
return { success: false, message: e.toString() };
}
}
/**
* Fungsi Utama Mengambil Data (DIPERBAIKI)
* Menghapus spasi pada header agar kompatibel dengan JavaScript Object
*/
function getJurnal() {
try {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(‘JurnalKebiasaan’);
if(!sheet) return { success: true, data: [] };
const data = sheet.getDataRange().getValues();
if (data.length <= 1) return { success: true, data: [] };
const headers = data.shift(); // Ambil baris pertama sebagai header
const cleanData = data.map(row => {
let obj = {};
headers.forEach((header, i) => {
// Hapus spasi dari header: “Nama Anak” -> “NamaAnak”
let key = header.toString().replace(/\s+/g, ”);
let val = row[i];
// Format Tanggal jika tipenya Date
if (val instanceof Date) {
val = Utilities.formatDate(val, Session.getScriptTimeZone(), “yyyy-MM-dd”);
}
obj[key] = (val === null || val === undefined) ? “” : val;
});
return obj;
});
return { success: true, data: cleanData.reverse() }; // Data terbaru di atas
} catch (e) {
return { success: false, message: e.toString() };
}
}
function updateJurnal(id, dataUpdate) {
try {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(‘JurnalKebiasaan’);
const data = sheet.getDataRange().getValues();
for (let i = 1; i < data.length; i++) {
if (data[i][0] == id) {
const row = i + 1;
sheet.getRange(row, 2).setValue(dataUpdate.tanggal);
sheet.getRange(row, 5, 1, 7).setValues([[
dataUpdate.habits[0] ? “✅” : “❌”,
dataUpdate.habits[1] ? “✅” : “❌”,
dataUpdate.habits[2] ? “✅” : “❌”,
dataUpdate.habits[3] ? “✅” : “❌”,
dataUpdate.habits[4] ? “✅” : “❌”,
dataUpdate.habits[5] ? “✅” : “❌”,
dataUpdate.habits[6] ? “✅” : “❌”
]]);
return { success: true, message: “Data berhasil diperbarui” };
}
}
return { success: false, message: “ID tidak ditemukan” };
} catch (e) {
return { success: false, message: e.toString() };
}
}
function hapusJurnal(id) {
try {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(‘JurnalKebiasaan’);
const data = sheet.getDataRange().getValues();
for (let i = 1; i < data.length; i++) {
if (data[i][0] == id) {
sheet.deleteRow(i + 1);
return { success: true, message: “Data berhasil dihapus” };
}
}
return { success: false, message: “ID tidak ditemukan” };
} catch (e) {
return { success: false, message: e.toString() };
}
}
Index.php
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
<title>Jurnal 7 Kebiasaan Anak Indonesia Hebat</title>
<script src=”https://cdn.tailwindcss.com”></script>
<link rel=”stylesheet” href=”https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css”>
<script src=”https://cdn.jsdelivr.net/npm/sweetalert2@11″></script>
<script src=”https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js”></script>
<script src=”https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.25/jspdf.plugin.autotable.min.js”></script>
<style>
@import url(‘https://fonts.googleapis.com/css2?family=Quicksand:wght@400;600;700&display=swap’);
body { font-family: ‘Quicksand’, sans-serif; background-color: #f8fafc; }
.sticky-col { position: sticky; left: 0; background: white; z-index: 10; border-right: 1px solid #e2e8f0; }
.loader { border: 3px solid #f3f3f3; border-top: 3px solid #4F46E5; border-radius: 50%; width: 20px; height: 20px; animation: spin 1s linear infinite; display: inline-block; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.hidden { display: none; }
input[type=”checkbox”] { width: 1.2rem; height: 1.2rem; cursor: pointer; }
@media print {
.no-print { display: none !important; }
.print-only { display: block !important; }
body { background-color: white; padding: 0; }
.container { max-width: 100%; width: 100%; padding: 0; margin: 0; }
.bg-white { shadow: none; border: none; }
table { font-size: 10px; }
}
</style>
</head>
<body class=”pb-10″>
<!– Header –>
<div class=”bg-indigo-700 text-white py-6 px-4 shadow-lg text-center mb-6 no-print”>
<h1 class=”text-2xl font-bold”>🏫 Jurnal 7 Kebiasaan Anak Indonesia Hebat</h1>
<p class=”text-indigo-100 text-sm”>SLB NEGERI 1 JEMBRANA</p>
</div>
<div class=”container mx-auto px-4 max-w-5xl”>
<!– Navigasi –>
<div class=”flex justify-center space-x-2 mb-6 no-print”>
<button onclick=”switchPage(‘form’)” id=”btn-nav-form” class=”px-5 py-2 rounded-full bg-indigo-600 text-white font-semibold shadow-md transition-all”>Input Kelas</button>
<button onclick=”switchPage(‘table’)” id=”btn-nav-table” class=”px-5 py-2 rounded-full bg-white text-indigo-600 font-semibold shadow-md border border-indigo-100 transition-all”>Riwayat</button>
</div>
<!– HALAMAN INPUT –>
<div id=”page-form” class=”bg-white rounded-2xl shadow-xl p-4 md:p-6 no-print”>
<div class=”grid grid-cols-1 md:grid-cols-3 gap-4 mb-6 items-end”>
<div>
<label class=”block text-xs font-bold text-gray-500 uppercase”>Tanggal Jurnal</label>
<input type=”date” id=”tglInput” class=”mt-1 block w-full py-2 px-3 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 sm:text-sm”>
</div>
<div>
<label class=”block text-xs font-bold text-gray-500 uppercase”>Pilih Kelas</label>
<select id=”filterKelasInput” onchange=”filterDaftarAnak()” class=”mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:ring-indigo-500 sm:text-sm”>
<option value=””>– Pilih Kelas –</option>
</select>
</div>
<div class=”flex gap-2″>
<button onclick=”checkAllRows()” class=”flex-1 bg-green-100 text-green-700 px-3 py-2 rounded-lg text-xs font-bold hover:bg-green-200 transition”>
<i class=”bi bi-check-all”></i> Ceklis Semua
</button>
</div>
</div>
<div class=”overflow-x-auto border rounded-xl”>
<table class=”w-full text-sm text-left”>
<thead class=”bg-gray-50 text-gray-600 font-bold uppercase text-[10px]”>
<tr>
<th class=”p-3 border-b sticky-col”>Nama Murid</th>
<th class=”p-2 border-b text-center”>☀️</th>
<th class=”p-2 border-b text-center”>🙏</th>
<th class=”p-2 border-b text-center”>⚽</th>
<th class=”p-2 border-b text-center”>🍎</th>
<th class=”p-2 border-b text-center”>📚</th>
<th class=”p-2 border-b text-center”>🤝</th>
<th class=”p-2 border-b text-center”>🌙</th>
</tr>
</thead>
<tbody id=”kolektifBody”>
<tr><td colspan=”8″ class=”p-10 text-center text-gray-400 italic”>Silakan pilih kelas terlebih dahulu…</td></tr>
</tbody>
</table>
</div>
<div class=”mt-8″>
<button onclick=”submitKolektif()” id=”btnSimpan” class=”w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-4 rounded-xl shadow-lg transition flex items-center justify-center”>
<span id=”btnText”><i class=”bi bi-cloud-upload mr-2″></i> Simpan Jurnal Kelas</span>
<div id=”btnLoading” class=”hidden”><div class=”loader”></div> Memproses…</div>
</button>
</div>
</div>
<!– HALAMAN RIWAYAT –>
<div id=”page-table” class=”hidden bg-white rounded-2xl shadow-xl p-6″>
<div class=”mb-6 p-4 bg-indigo-50 rounded-xl border border-indigo-100 no-print”>
<h3 class=”text-sm font-bold text-indigo-800 mb-3″><i class=”bi bi-printer”></i> Filter & Cetak Laporan</h3>
<div class=”grid grid-cols-1 md:grid-cols-4 gap-3 items-end”>
<div>
<label class=”block text-[10px] font-bold text-gray-500 uppercase”>Dari Tanggal</label>
<input type=”date” id=”pdfStart” class=”mt-1 block w-full py-1 px-2 text-xs border border-gray-300 rounded focus:ring-indigo-500″>
</div>
<div>
<label class=”block text-[10px] font-bold text-gray-500 uppercase”>Sampai Tanggal</label>
<input type=”date” id=”pdfEnd” class=”mt-1 block w-full py-1 px-2 text-xs border border-gray-300 rounded focus:ring-indigo-500″>
</div>
<div>
<label class=”block text-[10px] font-bold text-gray-500 uppercase”>Filter Kelas</label>
<select id=”filterKelasTabel” onchange=”applyFilters()” class=”mt-1 block w-full py-1 px-2 text-xs border border-gray-300 rounded focus:ring-indigo-500″>
<option value=””>Semua Kelas</option>
</select>
</div>
<div class=”flex gap-2″>
<button onclick=”downloadPDF()” class=”flex-1 bg-red-500 text-white px-3 py-2 rounded-lg text-[10px] font-bold shadow hover:bg-red-600 transition”>
<i class=”bi bi-file-earmark-pdf”></i> PDF
</button>
<button onclick=”window.print()” class=”flex-1 bg-gray-700 text-white px-3 py-2 rounded-lg text-[10px] font-bold shadow hover:bg-gray-800 transition”>
<i class=”bi bi-printer”></i> Cetak
</button>
</div>
</div>
</div>
<!– Header Cetak –>
<div class=”hidden print-only text-center mb-6″>
<h2 class=”text-xl font-bold”>LAPORAN JURNAL 7 KEBIASAAN</h2>
<p class=”text-sm”>SLB NEGERI 1 JEMBRANA</p>
<p id=”printRangeInfo” class=”text-xs mt-2 italic”></p>
<hr class=”my-4 border-gray-400″>
</div>
<div class=”flex justify-between items-center mb-4 no-print”>
<h2 class=”font-bold text-gray-800″>Riwayat Terakhir</h2>
<button onclick=”refreshData()” class=”text-indigo-600 text-xs font-bold”><i class=”bi bi-arrow-clockwise”></i> Refresh Data</button>
</div>
<div class=”overflow-x-auto”>
<table id=”mainTable” class=”w-full text-[11px] text-left border-collapse border border-gray-200″>
<thead>
<tr class=”bg-gray-100 text-gray-600″>
<th class=”p-2 border”>Tgl Jurnal</th>
<th class=”p-2 border”>Nama / Kelas</th>
<th class=”p-1 border text-center”>☀️</th>
<th class=”p-1 border text-center”>🙏</th>
<th class=”p-1 border text-center”>⚽</th>
<th class=”p-1 border text-center”>🍎</th>
<th class=”p-1 border text-center”>📚</th>
<th class=”p-1 border text-center”>🤝</th>
<th class=”p-1 border text-center”>🌙</th>
<th class=”p-1 border text-center no-print”>Aksi</th>
</tr>
</thead>
<tbody id=”tableBody”></tbody>
</table>
</div>
</div>
<div id=”sim-badge” class=”hidden mt-4 bg-orange-100 text-orange-800 text-center py-2 px-4 rounded-xl border border-orange-200 text-xs font-bold no-print”>
⚠️ MODE SIMULASI (Preview)
</div>
</div>
<!– MODAL EDIT –>
<div id=”editModal” class=”hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-[100] no-print”>
<div class=”bg-white rounded-2xl w-full max-w-md p-6 shadow-2xl”>
<h3 class=”text-lg font-bold text-indigo-800 mb-4″>Edit Jurnal</h3>
<input type=”hidden” id=”editId”>
<div class=”space-y-4″>
<div>
<label class=”block text-xs font-bold text-gray-400 uppercase”>Nama Anak</label>
<div id=”editNama” class=”font-bold text-gray-700″></div>
</div>
<div>
<label class=”block text-xs font-bold text-gray-400 uppercase”>Tanggal Jurnal</label>
<input type=”date” id=”editTanggal” class=”w-full p-2 border rounded mt-1″>
</div>
<div class=”grid grid-cols-2 gap-2″>
<label class=”flex items-center gap-2 p-2 border rounded”><input type=”checkbox” id=”e-0″> ☀️ Bangun</label>
<label class=”flex items-center gap-2 p-2 border rounded”><input type=”checkbox” id=”e-1″> 🙏 Ibadah</label>
<label class=”flex items-center gap-2 p-2 border rounded”><input type=”checkbox” id=”e-2″> ⚽ Olahraga</label>
<label class=”flex items-center gap-2 p-2 border rounded”><input type=”checkbox” id=”e-3″> 🍎 Makan</label>
<label class=”flex items-center gap-2 p-2 border rounded”><input type=”checkbox” id=”e-4″> 📚 Belajar</label>
<label class=”flex items-center gap-2 p-2 border rounded”><input type=”checkbox” id=”e-5″> 🤝 Sosial</label>
<label class=”flex items-center gap-2 p-2 border rounded”><input type=”checkbox” id=”e-6″> 🌙 Tidur</label>
</div>
</div>
<div class=”mt-6 flex gap-2″>
<button onclick=”closeEditModal()” class=”flex-1 bg-gray-200 text-gray-700 font-bold py-2 rounded-lg”>Batal</button>
<button onclick=”saveEdit()” class=”flex-1 bg-indigo-600 text-white font-bold py-2 rounded-lg shadow-md hover:bg-indigo-700″>Simpan Perubahan</button>
</div>
</div>
</div>
<script>
let daftarAnakGlobal = [];
let jurnalGlobal = [];
let listKelas = [];
window.onload = function() {
const today = new Date().toISOString().split(‘T’)[0];
document.getElementById(‘tglInput’).value = today;
document.getElementById(‘pdfStart’).value = today;
document.getElementById(‘pdfEnd’).value = today;
if (typeof google === ‘undefined’) {
document.getElementById(‘sim-badge’).classList.remove(‘hidden’);
initMock();
} else {
fetchDaftarAnak();
loadData();
}
};
function initMock() {
daftarAnakGlobal = [ [‘Budi Santoso’, ‘4-A’], [‘Siti Aminah’, ‘4-A’] ];
jurnalGlobal = [ { ID: ‘1’, TanggalInput: ‘2023-10-27’, NamaAnak: ‘Budi Santoso’, Kelas: ‘4-A’, BangunPagi: ‘✅’, Beribadah: ‘✅’, Berolahraga: ‘❌’, MakanSehat: ‘✅’, GemarBelajar: ‘✅’, Bermasyarakat: ‘❌’, TidurCepat: ‘✅’ } ];
processListKelas();
renderTable(jurnalGlobal);
}
function fetchDaftarAnak() {
google.script.run.withSuccessHandler(res => {
if(res.success) {
daftarAnakGlobal = res.data;
processListKelas();
}
}).getDaftarAnak();
}
function processListKelas() {
listKelas = […new Set(daftarAnakGlobal.map(item => item[1]))].sort();
document.getElementById(‘filterKelasInput’).innerHTML = ‘<option value=””>– Pilih Kelas –</option>’ + listKelas.map(k => `<option value=”${k}”>${k}</option>`).join(”);
document.getElementById(‘filterKelasTabel’).innerHTML = ‘<option value=””>Semua Kelas</option>’ + listKelas.map(k => `<option value=”${k}”>${k}</option>`).join(”);
}
function filterDaftarAnak() {
const kelas = document.getElementById(‘filterKelasInput’).value;
const body = document.getElementById(‘kolektifBody’);
if (!kelas) {
body.innerHTML = ‘<tr><td colspan=”8″ class=”p-10 text-center text-gray-400 italic”>Silakan pilih kelas…</td></tr>’;
return;
}
const filtered = daftarAnakGlobal.filter(a => a[1] === kelas);
body.innerHTML = filtered.map(a => `
<tr class=”border-b hover:bg-gray-50 row-murid” data-nama=”${a[0]}” data-kelas=”${a[1]}”>
<td class=”p-3 font-semibold text-gray-700 sticky-col”>${a[0]}</td>
<td class=”p-2 text-center”><input type=”checkbox” class=”h-item”></td>
<td class=”p-2 text-center”><input type=”checkbox” class=”h-item”></td>
<td class=”p-2 text-center”><input type=”checkbox” class=”h-item”></td>
<td class=”p-2 text-center”><input type=”checkbox” class=”h-item”></td>
<td class=”p-2 text-center”><input type=”checkbox” class=”h-item”></td>
<td class=”p-2 text-center”><input type=”checkbox” class=”h-item”></td>
<td class=”p-2 text-center”><input type=”checkbox” class=”h-item”></td>
</tr>
`).join(”);
}
function checkAllRows() { document.querySelectorAll(‘.h-item’).forEach(c => c.checked = true); }
function applyFilters() {
const start = document.getElementById(‘pdfStart’).value;
const end = document.getElementById(‘pdfEnd’).value;
const kelas = document.getElementById(‘filterKelasTabel’).value;
let filtered = jurnalGlobal;
if(start && end) filtered = filtered.filter(row => row.TanggalInput >= start && row.TanggalInput <= end);
if(kelas) filtered = filtered.filter(row => row.Kelas === kelas);
renderTable(filtered);
}
function submitKolektif() {
const rows = document.querySelectorAll(‘.row-murid’);
const tgl = document.getElementById(‘tglInput’).value;
if (!tgl || rows.length === 0) return Swal.fire(‘Error’, ‘Lengkapi tanggal dan kelas!’, ‘warning’);
const payload = [];
rows.forEach(row => {
const habits = [];
row.querySelectorAll(‘.h-item’).forEach(c => habits.push(c.checked));
payload.push({ nama: row.getAttribute(‘data-nama’), kelas: row.getAttribute(‘data-kelas’), habits: habits });
});
startLoading();
if (typeof google === ‘undefined’) {
setTimeout(() => {
Swal.fire(‘Simulasi’, ‘Data tersimpan’, ‘success’);
endLoading(); switchPage(‘table’);
}, 800);
} else {
google.script.run.withSuccessHandler(res => {
endLoading();
if(res.success) { Swal.fire(‘Selesai!’, res.message, ‘success’); switchPage(‘table’); }
}).simpanJurnalKolektif(payload, tgl);
}
}
function switchPage(page) {
document.getElementById(‘page-form’).classList.toggle(‘hidden’, page !== ‘form’);
document.getElementById(‘page-table’).classList.toggle(‘hidden’, page !== ‘table’);
const btnF = document.getElementById(‘btn-nav-form’);
const btnT = document.getElementById(‘btn-nav-table’);
if(page === ‘form’) {
btnF.className = “px-5 py-2 rounded-full bg-indigo-600 text-white font-semibold shadow-md”;
btnT.className = “px-5 py-2 rounded-full bg-white text-indigo-600 font-semibold shadow-md border border-indigo-100”;
} else {
btnT.className = “px-5 py-2 rounded-full bg-indigo-600 text-white font-semibold shadow-md”;
btnF.className = “px-5 py-2 rounded-full bg-white text-indigo-600 font-semibold shadow-md border border-indigo-100”;
loadData();
}
}
function loadData() {
if (typeof google === ‘undefined’) return;
google.script.run.withSuccessHandler(res => {
if(res.success) { jurnalGlobal = res.data; applyFilters(); }
}).getJurnal();
}
function renderTable(data) {
const body = document.getElementById(‘tableBody’);
if (!data || data.length === 0) {
body.innerHTML = ‘<tr><td colspan=”10″ class=”p-10 text-center text-gray-400″>Data tidak ditemukan</td></tr>’;
return;
}
body.innerHTML = data.map(row => `
<tr class=”border-b hover:bg-gray-50″>
<td class=”p-2 text-gray-500 font-mono border”>${row.TanggalInput}</td>
<td class=”p-2 font-bold text-gray-700 border”>${row.NamaAnak} <span class=”text-[9px] block text-gray-400 font-normal uppercase”>${row.Kelas}</span></td>
<td class=”p-1 text-center border”>${row.BangunPagi}</td>
<td class=”p-1 text-center border”>${row.Beribadah}</td>
<td class=”p-1 text-center border”>${row.Berolahraga}</td>
<td class=”p-1 text-center border”>${row.MakanSehat}</td>
<td class=”p-1 text-center border”>${row.GemarBelajar}</td>
<td class=”p-1 text-center border”>${row.Bermasyarakat}</td>
<td class=”p-1 text-center border”>${row.TidurCepat}</td>
<td class=”p-1 text-center border no-print”>
<div class=”flex justify-center gap-1″>
<button onclick=”openEdit(‘${row.ID}’)” class=”text-indigo-600 p-1 hover:bg-indigo-50 rounded”><i class=”bi bi-pencil-square”></i></button>
<button onclick=”hapusData(‘${row.ID}’)” class=”text-red-600 p-1 hover:bg-red-50 rounded”><i class=”bi bi-trash”></i></button>
</div>
</td>
</tr>
`).join(”);
}
// AKSI EDIT
function openEdit(id) {
const item = jurnalGlobal.find(j => j.ID == id);
if(!item) return;
document.getElementById(‘editId’).value = id;
document.getElementById(‘editNama’).innerText = item.NamaAnak + ” (” + item.Kelas + “)”;
document.getElementById(‘editTanggal’).value = item.TanggalInput;
document.getElementById(‘e-0’).checked = item.BangunPagi === ‘✅’;
document.getElementById(‘e-1’).checked = item.Beribadah === ‘✅’;
document.getElementById(‘e-2’).checked = item.Berolahraga === ‘✅’;
document.getElementById(‘e-3’).checked = item.MakanSehat === ‘✅’;
document.getElementById(‘e-4’).checked = item.GemarBelajar === ‘✅’;
document.getElementById(‘e-5’).checked = item.Bermasyarakat === ‘✅’;
document.getElementById(‘e-6’).checked = item.TidurCepat === ‘✅’;
document.getElementById(‘editModal’).classList.remove(‘hidden’);
}
function closeEditModal() { document.getElementById(‘editModal’).classList.add(‘hidden’); }
function saveEdit() {
const id = document.getElementById(‘editId’).value;
const data = {
tanggal: document.getElementById(‘editTanggal’).value,
habits: [
document.getElementById(‘e-0’).checked, document.getElementById(‘e-1’).checked,
document.getElementById(‘e-2’).checked, document.getElementById(‘e-3’).checked,
document.getElementById(‘e-4’).checked, document.getElementById(‘e-5’).checked,
document.getElementById(‘e-6’).checked
]
};
Swal.fire({ title: ‘Menyimpan…’, allowOutsideClick: false, didOpen: () => Swal.showLoading() });
if(typeof google === ‘undefined’) {
Swal.fire(‘Berhasil’, ‘Simulasi update selesai’, ‘success’);
closeEditModal();
} else {
google.script.run.withSuccessHandler(res => {
Swal.fire(res.success ? ‘Berhasil’ : ‘Gagal’, res.message, res.success ? ‘success’ : ‘error’);
if(res.success) { closeEditModal(); loadData(); }
}).updateJurnal(id, data);
}
}
// AKSI HAPUS
function hapusData(id) {
Swal.fire({
title: ‘Hapus Data?’,
text: “Data yang dihapus tidak bisa dikembalikan!”,
icon: ‘warning’,
showCancelButton: true,
confirmButtonColor: ‘#d33’,
cancelButtonColor: ‘#3085d6’,
confirmButtonText: ‘Ya, Hapus!’
}).then((result) => {
if (result.isConfirmed) {
Swal.fire({ title: ‘Menghapus…’, allowOutsideClick: false, didOpen: () => Swal.showLoading() });
if(typeof google === ‘undefined’) {
Swal.fire(‘Dihapus’, ‘Simulasi hapus selesai’, ‘success’);
} else {
google.script.run.withSuccessHandler(res => {
Swal.fire(res.success ? ‘Dihapus!’ : ‘Gagal’, res.message, res.success ? ‘success’ : ‘error’);
if(res.success) loadData();
}).hapusJurnal(id);
}
}
});
}
function downloadPDF() {
const start = document.getElementById(‘pdfStart’).value;
const end = document.getElementById(‘pdfEnd’).value;
const kelas = document.getElementById(‘filterKelasTabel’).value;
let filteredData = jurnalGlobal;
if (start && end) filteredData = filteredData.filter(row => row.TanggalInput >= start && row.TanggalInput <= end);
if (kelas) filteredData = filteredData.filter(row => row.Kelas === kelas);
filteredData.sort((a,b) => a.TanggalInput.localeCompare(b.TanggalInput));
if (filteredData.length === 0) return Swal.fire(‘Kosong’, ‘Tidak ada data’, ‘info’);
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
doc.setFontSize(14); doc.text(‘LAPORAN JURNAL 7 KEBIASAAN’, 14, 15);
doc.setFontSize(9); doc.text(`Periode: ${start} s/d ${end} | Kelas: ${kelas || ‘Semua’}`, 14, 22);
const tableData = filteredData.map(row => [ row.TanggalInput, row.NamaAnak, row.Kelas, row.BangunPagi, row.Beribadah, row.Berolahraga, row.MakanSehat, row.GemarBelajar, row.Bermasyarakat, row.TidurCepat ]);
doc.autoTable({ startY: 28, head: [[‘Tgl’, ‘Nama’, ‘Kls’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’]], body: tableData, styles: { fontSize: 7 }, headStyles: { fillColor: [67, 56, 202] } });
doc.save(`Laporan_Jurnal.pdf`);
}
function startLoading() { document.getElementById(‘btnSimpan’).disabled = true; document.getElementById(‘btnText’).classList.add(‘hidden’); document.getElementById(‘btnLoading’).classList.remove(‘hidden’); }
function endLoading() { document.getElementById(‘btnSimpan’).disabled = false; document.getElementById(‘btnText’).classList.remove(‘hidden’); document.getElementById(‘btnLoading’).classList.add(‘hidden’); }
function refreshData() { loadData(); }
</script>
</body>
</html>
