<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Monev SMA Double Track</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700&display=swap');
body { font-family: 'Plus Jakarta Sans', sans-serif; scroll-behavior: smooth; }
.hero-gradient { background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%); }
.glass-nav { background: rgba(255, 255, 255, 0.8); backdrop-filter: blur(10px); }
.loading-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255,255,255,0.95); z-index: 9999; justify-content: center; align-items: center; }
@media print { .no-print { display: none !important; } .print-only { display: block !important; } }
.print-only { display: none; }
</style>
</head>
<body class="bg-slate-50 min-h-screen">
<!-- Loading Animation -->
<div id="loading" class="loading-overlay">
<div class="text-center">
<div class="animate-spin rounded-full h-16 w-16 border-t-4 border-blue-600 border-opacity-50 mx-auto"></div>
<p id="loading-text" class="mt-4 font-bold text-slate-700">Sedang Memproses...</p>
</div>
</div>
<!-- Navigation -->
<nav class="glass-nav sticky top-0 z-50 border-b border-slate-200 no-print">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16 items-center">
<div class="flex items-center gap-2 cursor-pointer" onclick="navigate('landing')">
<div class="bg-blue-600 p-2 rounded-lg text-white"><i class="fas fa-graduation-cap"></i></div>
<span class="font-bold text-xl text-slate-800 tracking-tight">Monev SMA DT</span>
</div>
<div class="hidden md:flex space-x-6 text-sm font-semibold text-slate-600">
<button onclick="navigate('landing')" class="hover:text-blue-600 transition">Beranda</button>
<button onclick="navigate('dashboard')" class="hover:text-blue-600 transition">Dashboard</button>
<div class="relative group">
<button class="hover:text-blue-600 transition flex items-center gap-1">Input Data <i class="fas fa-chevron-down text-[10px]"></i></button>
<div class="absolute right-0 w-48 bg-white border rounded-xl shadow-xl opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all p-2 space-y-1">
<button onclick="navigate('input-siswa')" class="w-full text-left p-2 hover:bg-slate-50 rounded-lg">Data Siswa</button>
<button onclick="navigate('input-sekolah')" class="w-full text-left p-2 hover:bg-slate-50 rounded-lg">Data Sekolah</button>
<button onclick="navigate('input-trainer')" class="w-full text-left p-2 hover:bg-slate-50 rounded-lg">Data Trainer</button>
<button onclick="navigate('input-jurnal')" class="w-full text-left p-2 hover:bg-slate-50 rounded-lg">Jurnal Pelatihan</button>
</div>
</div>
</div>
<button onclick="navigate('dashboard')" class="bg-blue-600 text-white px-5 py-2 rounded-full text-sm font-bold shadow-lg hover:shadow-blue-200 transition">Lihat Laporan</button>
</div>
</div>
</nav>
<!-- CONTENT WRAPPER -->
<div id="main-content">
<!-- PAGE: LANDING -->
<section id="page-landing" class="page-section">
<div class="hero-gradient text-white py-24 px-6 relative overflow-hidden">
<div class="max-w-7xl mx-auto flex flex-col md:flex-row items-center justify-between gap-12 relative z-10">
<div class="md:w-1/2">
<span class="bg-blue-500 bg-opacity-30 text-blue-100 px-4 py-1 rounded-full text-xs font-bold uppercase tracking-widest">Sistem Informasi SMA DT</span>
<h1 class="text-5xl md:text-6xl font-bold mt-4 leading-tight">Monitoring & Evaluasi <br><span class="text-blue-200">SMA Double Track</span></h1>
<p class="text-lg text-blue-100 mt-6 leading-relaxed">Kelola data pelatihan siswa, sertifikasi, dan jurnal harian dalam satu platform terintegrasi. Real-time, Akurat, dan Profesional.</p>
<div class="mt-10 flex gap-4">
<button onclick="navigate('dashboard')" class="bg-white text-blue-600 px-8 py-4 rounded-xl font-bold shadow-xl hover:scale-105 transition">Masuk Dashboard</button>
<button onclick="navigate('input-jurnal')" class="border-2 border-white border-opacity-30 px-8 py-4 rounded-xl font-bold hover:bg-white hover:text-blue-600 transition">Input Jurnal</button>
</div>
</div>
<div class="md:w-1/2 flex justify-center">
<div class="relative">
<div class="absolute -top-10 -left-10 w-40 h-40 bg-blue-400 rounded-full mix-blend-multiply filter blur-2xl opacity-30 animate-pulse"></div>
<div class="bg-white p-8 rounded-3xl shadow-2xl border-4 border-blue-400 border-opacity-20">
<i class="fas fa-chart-pie text-[100px] text-blue-500"></i>
<div class="mt-6 text-center text-slate-800">
<p class="text-3xl font-bold">100%</p>
<p class="text-sm font-semibold text-slate-500 uppercase">Akurasi Data</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- PAGE: INPUT DATA (Common Form Container) -->
<section id="page-input-form" class="page-section hidden py-16 px-6">
<div class="max-w-2xl mx-auto">
<div class="bg-white rounded-3xl shadow-2xl border border-slate-100 overflow-hidden">
<div id="form-header" class="bg-slate-50 p-8 border-b">
<h2 id="form-title" class="text-2xl font-bold text-slate-800">Input Data</h2>
<p id="form-desc" class="text-slate-500 text-sm mt-1">Silakan isi formulir dengan lengkap.</p>
</div>
<div class="p-8">
<!-- FORM SISWA -->
<form id="form-siswa" class="space-y-4 hidden" onsubmit="submitForm(event, 'siswa')">
<input type="text" name="nama" placeholder="Nama Lengkap Siswa" required class="w-full p-4 bg-slate-50 border rounded-2xl focus:ring-2 focus:ring-blue-500">
<input type="text" name="sekolah" placeholder="Nama Sekolah" required class="w-full p-4 bg-slate-50 border rounded-2xl">
<div class="grid grid-cols-2 gap-4">
<input type="text" name="kelas" placeholder="Kelas" required class="w-full p-4 bg-slate-50 border rounded-2xl">
<input type="text" name="bidang" placeholder="Bidang Keahlian" required class="w-full p-4 bg-slate-50 border rounded-2xl">
</div>
<input type="number" name="jam_pelatihan" placeholder="Total Jam Pelatihan" required class="w-full p-4 bg-slate-50 border rounded-2xl">
<select name="status_sertifikasi" class="w-full p-4 bg-slate-50 border rounded-2xl">
<option>Terdaftar</option>
<option>Lulus</option>
<option>Tidak Lulus</option>
</select>
<button type="submit" class="w-full bg-blue-600 text-white font-bold py-4 rounded-2xl shadow-lg">Simpan Data Siswa</button>
</form>
<!-- FORM SEKOLAH -->
<form id="form-sekolah" class="space-y-4 hidden" onsubmit="submitForm(event, 'sekolah')">
<input type="text" name="nama_sekolah" placeholder="Nama Sekolah" required class="w-full p-4 bg-slate-50 border rounded-2xl">
<input type="text" name="npsn" placeholder="NPSN" required class="w-full p-4 bg-slate-50 border rounded-2xl">
<input type="text" name="alamat" placeholder="Alamat Sekolah" required class="w-full p-4 bg-slate-50 border rounded-2xl">
<input type="text" name="kepala_sekolah" placeholder="Nama Kepala Sekolah" required class="w-full p-4 bg-slate-50 border rounded-2xl">
<button type="submit" class="w-full bg-slate-800 text-white font-bold py-4 rounded-2xl shadow-lg">Daftarkan Sekolah</button>
</form>
<!-- FORM TRAINER -->
<form id="form-trainer" class="space-y-4 hidden" onsubmit="submitForm(event, 'trainer')">
<input type="text" name="nama_trainer" placeholder="Nama Trainer" required class="w-full p-4 bg-slate-50 border rounded-2xl">
<input type="text" name="spesialisasi" placeholder="Spesialisasi" required class="w-full p-4 bg-slate-50 border rounded-2xl">
<input type="text" name="sekolah_asal" placeholder="Sekolah Asal" required class="w-full p-4 bg-slate-50 border rounded-2xl">
<input type="text" name="kontak" placeholder="Kontak (WA/Email)" required class="w-full p-4 bg-slate-50 border rounded-2xl">
<button type="submit" class="w-full bg-indigo-600 text-white font-bold py-4 rounded-2xl shadow-lg">Simpan Data Trainer</button>
</form>
<!-- FORM JURNAL (DENGAN UPLOAD FOTO) -->
<form id="form-jurnal" class="space-y-4 hidden" onsubmit="submitForm(event, 'jurnal')">
<input type="text" name="sekolah" placeholder="Nama Sekolah" required class="w-full p-4 bg-slate-50 border rounded-2xl">
<input type="text" name="bidang" placeholder="Bidang Keahlian" required class="w-full p-4 bg-slate-50 border rounded-2xl">
<textarea name="kegiatan" placeholder="Deskripsi Jurnal Pelatihan Hari Ini" required class="w-full p-4 bg-slate-50 border rounded-2xl h-32"></textarea>
<input type="number" name="jam" placeholder="Jumlah Jam Pelatihan" required class="w-full p-4 bg-slate-50 border rounded-2xl">
<div class="border-2 border-dashed border-slate-200 p-6 rounded-2xl text-center hover:bg-slate-50 transition cursor-pointer relative">
<input type="file" id="foto-jurnal" accept="image/*" class="absolute inset-0 opacity-0 cursor-pointer" onchange="previewImg(event)">
<div id="upload-placeholder">
<i class="fas fa-camera text-3xl text-slate-300 mb-2"></i>
<p class="text-sm text-slate-500">Ketuk untuk pilih foto pelatihan</p>
</div>
<img id="jurnal-preview" class="hidden mx-auto h-40 rounded-xl shadow-md border-4 border-white">
</div>
<button type="submit" class="w-full bg-teal-600 text-white font-bold py-4 rounded-2xl shadow-lg">Unggah Jurnal & Foto</button>
</form>
</div>
</div>
</div>
</section>
<!-- PAGE: DASHBOARD -->
<section id="page-dashboard" class="page-section hidden py-12 px-6">
<div class="max-w-7xl mx-auto">
<div class="flex justify-between items-end mb-8 no-print">
<div>
<h2 class="text-3xl font-bold text-slate-800">Dashboard Real-time</h2>
<p class="text-slate-500">Pantau progres Double Track seluruh sekolah.</p>
</div>
<button onclick="window.print()" class="bg-white border p-3 rounded-xl shadow-sm hover:bg-slate-50 transition font-bold text-slate-700 flex items-center gap-2">
<i class="fas fa-print"></i> Cetak Laporan
</button>
</div>
<!-- KPI CARDS -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<div class="bg-white p-6 rounded-3xl border shadow-sm">
<p class="text-slate-500 text-sm font-semibold uppercase">Total Siswa</p>
<h3 id="dash-total-siswa" class="text-4xl font-bold mt-2">0</h3>
</div>
<div class="bg-white p-6 rounded-3xl border shadow-sm">
<p class="text-slate-500 text-sm font-semibold uppercase">% Sertifikasi</p>
<h3 id="dash-sertif-rate" class="text-4xl font-bold mt-2 text-green-600">0%</h3>
</div>
<div class="bg-white p-6 rounded-3xl border shadow-sm">
<p class="text-slate-500 text-sm font-semibold uppercase">Jml Sekolah</p>
<h3 id="dash-total-sekolah" class="text-4xl font-bold mt-2 text-blue-600">0</h3>
</div>
<div class="bg-white p-6 rounded-3xl border shadow-sm">
<p class="text-slate-500 text-sm font-semibold uppercase">Trainer</p>
<h3 id="dash-total-trainer" class="text-4xl font-bold mt-2 text-purple-600">0</h3>
</div>
</div>
<!-- CHARTS -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8 no-print">
<div class="bg-white p-8 rounded-3xl border shadow-sm h-[400px]">
<h4 class="font-bold mb-4">Siswa per Bidang Keahlian</h4>
<canvas id="chart-bidang"></canvas>
</div>
<div class="bg-white p-8 rounded-3xl border shadow-sm overflow-hidden h-[400px] flex flex-col">
<h4 class="font-bold mb-4">Aktivitas Jurnal Terbaru</h4>
<div id="list-jurnal" class="flex-1 overflow-y-auto space-y-4 pr-2">
<!-- Injected Jurnal -->
</div>
</div>
</div>
<!-- DETAILED TABLE FOR PRINT -->
<div class="bg-white rounded-3xl border shadow-sm overflow-hidden">
<div class="p-6 border-b bg-slate-50 flex justify-between items-center no-print">
<h4 class="font-bold text-slate-700 uppercase text-sm tracking-widest">Detail Data Peserta Sertifikasi</h4>
<button onclick="refreshDashboard()" class="text-blue-600 font-bold text-xs">REFRESH DATA</button>
</div>
<div class="p-4 print-only text-center mb-6">
<h1 class="text-2xl font-bold">LAPORAN MONITORING SMA DOUBLE TRACK</h1>
<p>Tanggal Cetak: <span id="print-date"></span></p>
</div>
<div class="overflow-x-auto">
<table class="w-full text-left">
<thead class="bg-slate-100 text-slate-500 font-bold text-xs uppercase">
<tr>
<th class="px-6 py-4">Nama</th>
<th class="px-6 py-4">Sekolah</th>
<th class="px-6 py-4">Bidang</th>
<th class="px-6 py-4">Jam</th>
<th class="px-6 py-4">Status</th>
</tr>
</thead>
<tbody id="table-laporan-detail" class="divide-y text-sm">
<!-- Injected Table -->
</tbody>
</table>
</div>
</div>
</div>
</section>
</div>
<!-- Toast Notification -->
<div id="toast" class="fixed bottom-10 left-1/2 -translate-x-1/2 bg-slate-800 text-white px-8 py-4 rounded-full shadow-2xl transition-all translate-y-24 opacity-0 z-[10000] font-bold flex items-center gap-3">
<i id="toast-icon" class="fas fa-check-circle text-green-400"></i>
<span id="toast-msg"></span>
</div>
<script>
// --- ROUTING SYSTEM ---
function navigate(page) {
document.querySelectorAll('.page-section').forEach(p => p.classList.add('hidden'));
if(page === 'landing') {
document.getElementById('page-landing').classList.remove('hidden');
} else if(page === 'dashboard') {
document.getElementById('page-dashboard').classList.remove('hidden');
refreshDashboard();
} else if(page.startsWith('input-')) {
const type = page.split('-')[1];
document.getElementById('page-input-form').classList.remove('hidden');
setupInputPage(type);
}
window.scrollTo(0,0);
}
function setupInputPage(type) {
document.querySelectorAll('#page-input-form form').forEach(f => f.classList.add('hidden'));
document.getElementById('form-' + type).classList.remove('hidden');
const titles = {
siswa: "Input Data Siswa",
sekolah: "Registrasi Sekolah Baru",
trainer: "Data Trainer & Mentor",
jurnal: "Input Jurnal Harian Pelatihan"
};
const descs = {
siswa: "Tambahkan peserta baru dalam program Double Track.",
sekolah: "Daftarkan sekolah penyelenggara program.",
trainer: "Kelola data tenaga pengajar dan instruktur.",
jurnal: "Laporkan aktivitas pelatihan harian beserta dokumentasi foto."
};
document.getElementById('form-title').innerText = titles[type];
document.getElementById('form-desc').innerText = descs[type];
}
// --- DASHBOARD SYSTEM ---
let charts = {};
function refreshDashboard() {
setLoading(true, "Sinkronisasi Data...");
google.script.run
.withSuccessHandler(data => {
setLoading(false);
if(data.error) return showToast(data.error, 'error');
// Stats
document.getElementById('dash-total-siswa').innerText = data.stats.totalSiswa;
document.getElementById('dash-sertif-rate').innerText = data.stats.sertifikasiRate + "%";
document.getElementById('dash-total-sekolah').innerText = data.stats.totalSekolah;
document.getElementById('dash-total-trainer').innerText = data.stats.totalTrainer;
// Jurnal List
const listJurnal = document.getElementById('list-jurnal');
listJurnal.innerHTML = data.recentJurnal.length ? '' : '<p class="text-slate-400 text-center py-10">Belum ada jurnal.</p>';
data.recentJurnal.forEach(j => {
listJurnal.innerHTML += `
<div class="flex gap-4 p-4 bg-slate-50 rounded-2xl hover:bg-white border border-transparent hover:border-slate-100 transition shadow-sm">
<img src="${j.foto_url}" class="w-16 h-16 rounded-xl object-cover bg-slate-200" onerror="this.src='https://via.placeholder.com/64'">
<div>
<p class="font-bold text-slate-800 text-sm leading-tight">${j.kegiatan}</p>
<p class="text-xs text-slate-500 mt-1">${j.sekolah} • ${j.bidang}</p>
<p class="text-[10px] text-blue-600 font-bold mt-1">${new Date(j.tanggal).toLocaleDateString('id-ID')}</p>
</div>
</div>
`;
});
// Detail Table for Print
const tableDetail = document.getElementById('table-laporan-detail');
tableDetail.innerHTML = '';
data.allSiswa.forEach(s => {
tableDetail.innerHTML += `
<tr>
<td class="px-6 py-4 font-semibold">${s.nama}</td>
<td class="px-6 py-4">${s.sekolah}</td>
<td class="px-6 py-4">${s.bidang}</td>
<td class="px-6 py-4 font-bold text-blue-600">${s.jam_pelatihan}h</td>
<td class="px-6 py-4 font-bold ${s.status_sertifikasi === 'Lulus' ? 'text-green-600' : 'text-orange-500'}">${s.status_sertifikasi}</td>
</tr>
`;
});
document.getElementById('print-date').innerText = new Date().toLocaleString('id-ID');
// Bar Chart
const ctx = document.getElementById('chart-bidang').getContext('2d');
if(charts.bidang) charts.bidang.destroy();
charts.bidang = new Chart(ctx, {
type: 'bar',
data: {
labels: Object.keys(data.bidangCounts),
datasets: [{ label: 'Jumlah Siswa', data: Object.values(data.bidangCounts), backgroundColor: '#3b82f6', borderRadius: 8 }]
},
options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, grid: { display: false } }, x: { grid: { display: false } } } }
});
})
.getDashboardData();
}
// --- FORM SUBMISSION ---
async function submitForm(e, type) {
e.preventDefault();
const form = e.target;
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
setLoading(true, "Menyimpan Data...");
if(type === 'jurnal') {
const fileInput = document.getElementById('foto-jurnal');
if(fileInput.files[0]) {
setLoading(true, "Mengompresi Gambar...");
const compressed = await compressImage(fileInput.files[0]);
data.fileData = compressed.base64;
data.fileName = fileInput.files[0].name;
data.fileMimeType = "image/jpeg";
}
}
google.script.run
.withSuccessHandler(res => {
setLoading(false);
if(res.success) {
showToast(res.message, 'success');
form.reset();
document.getElementById('jurnal-preview').classList.add('hidden');
document.getElementById('upload-placeholder').classList.remove('hidden');
} else {
showToast(res.message, 'error');
}
})
.saveFormData(type, data);
}
// --- IMAGE UTILS ---
function previewImg(e) {
const file = e.target.files[0];
if(!file) return;
const preview = document.getElementById('jurnal-preview');
const placeholder = document.getElementById('upload-placeholder');
preview.src = URL.createObjectURL(file);
preview.classList.remove('hidden');
placeholder.classList.add('hidden');
}
function compressImage(file) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = e => {
const img = new Image();
img.src = e.target.result;
img.onload = () => {
const canvas = document.createElement('canvas');
let w = img.width, h = img.height;
const max = 1000;
if(w > max) { h *= max/w; w = max; }
canvas.width = w; canvas.height = h;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, w, h);
resolve({ base64: canvas.toDataURL('image/jpeg', 0.8).split(',')[1] });
};
};
});
}
// --- UI HELPERS ---
function setLoading(s, text) {
const l = document.getElementById('loading');
l.style.display = s ? 'flex' : 'none';
document.getElementById('loading-text').innerText = text || "Sedang Memproses...";
}
function showToast(msg, type) {
const t = document.getElementById('toast');
document.getElementById('toast-msg').innerText = msg;
document.getElementById('toast-icon').className = type === 'success' ? 'fas fa-check-circle text-green-400' : 'fas fa-times-circle text-red-400';
t.classList.remove('translate-y-24', 'opacity-0');
setTimeout(() => t.classList.add('translate-y-24', 'opacity-0'), 3000);
}
// Mocking GAS for Canvas Preview
if (typeof google === 'undefined') {
window.google = { script: { run: {
withSuccessHandler: function(cb){this.scb=cb; return this;},
getDashboardData: function(){ setTimeout(()=>this.scb({stats:{totalSiswa:120,totalSekolah:15,totalTrainer:8,sertifikasiRate:75},bidangCounts:{"Boga":45,"Teknik":30},recentJurnal:[],allSiswa:[]}),500); },
saveFormData: function(){ setTimeout(()=>this.scb({success:true,message:"Mock Simpan Berhasil"}),800); }
}}};
}
</script>
</body>
</html>