<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Portal Nilai Informatika</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<!-- SheetJS for Excel file parsing support -->
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<style>
body { font-family: 'Plus Jakarta Sans', sans-serif; }
.fade-in { animation: fadeIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.tech-gradient {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
}
.card-glass {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(12px);
}
</style>
</head>
<body class="bg-slate-900 text-slate-100 min-h-screen flex flex-col selection:bg-cyan-500 selection:text-white">
<header class="tech-gradient border-b border-slate-800 text-white sticky top-0 z-40 shadow-xl">
<div class="max-w-7xl mx-auto px-4 sm:px-6 py-4 flex flex-col sm:flex-row justify-between items-center gap-4">
<div class="flex items-center space-x-3.5">
<div class="bg-gradient-to-br from-cyan-500 to-indigo-600 p-3 rounded-2xl shadow-lg shadow-cyan-500/20 text-white">
<i class="fa-solid fa-code text-2xl"></i>
</div>
<div>
<div class="flex items-center gap-2">
<h1 class="text-xl font-extrabold tracking-tight bg-gradient-to-r from-cyan-400 via-sky-300 to-indigo-300 bg-clip-text text-transparent">Portal Nilai Informatika</h1>
<span class="px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider bg-cyan-500/20 text-cyan-400 rounded-full border border-cyan-500/30">v2.8 Pro</span>
</div>
<p class="text-xs text-slate-400 font-medium">Sistem Evaluasi & Manajemen Nilai Kompetensi Keahlian</p>
</div>
</div>
<div class="flex items-center gap-3">
<!-- Quick Portal Switcher Bar -->
<div class="flex bg-slate-900/90 p-1.5 rounded-2xl border border-slate-700/80 shadow-inner">
<button onclick="switchPortal('teacher')" id="navBtnTeacher" class="px-3.5 py-1.5 rounded-xl text-xs font-bold transition-all flex items-center gap-2 text-slate-400 hover:text-white">
<i class="fa-solid fa-terminal text-cyan-400"></i> Portal Guru
</button>
<button onclick="switchPortal('student')" id="navBtnStudent" class="px-3.5 py-1.5 rounded-xl text-xs font-bold transition-all flex items-center gap-2 text-slate-400 hover:text-white">
<i class="fa-solid fa-laptop-code text-sky-400"></i> Portal Siswa
</button>
<button onclick="backToSelector()" id="navBtnSelector" class="px-3 py-1.5 rounded-xl text-xs font-bold transition-all flex items-center gap-1.5 text-slate-400 hover:text-white" title="Pilih Akses">
<i class="fa-solid fa-shield-halved text-indigo-400"></i>
</button>
</div>
<div id="roleIndicator" class="hidden sm:flex items-center bg-slate-800/90 px-4 py-2 rounded-xl text-xs font-semibold border border-slate-700/80 text-slate-300 shadow-inner">
<span id="currentRoleText" class="flex items-center gap-2">
<i class="fa-solid fa-shield-halved text-cyan-400"></i> Pilih Portal
</span>
</div>
</div>
</div>
</header>
<main class="flex-grow max-w-7xl w-full mx-auto px-4 sm:px-6 py-8">
<!-- Streamlined Modal / Custom Alert Notification Container -->
<div id="customModal" class="fixed inset-0 bg-slate-950/80 backdrop-blur-md z-50 hidden flex items-center justify-center p-4">
<div class="bg-slate-900 border border-slate-800 rounded-3xl max-w-md w-full p-6 sm:p-8 shadow-2xl fade-in text-slate-100">
<div id="modalIcon" class="w-14 h-14 rounded-2xl bg-cyan-500/10 text-cyan-400 flex items-center justify-center text-2xl mb-5 border border-cyan-500/20 shadow-lg shadow-cyan-500/10">
<i class="fa-solid fa-circle-info"></i>
</div>
<h3 id="modalTitle" class="text-xl font-bold text-white mb-2">Informasi Sistem</h3>
<p id="modalMessage" class="text-sm text-slate-400 mb-8 leading-relaxed">Pesan sistem...</p>
<div class="flex justify-end gap-3">
<button id="modalCancelBtn" onclick="closeModal(false)" class="hidden px-5 py-2.5 text-sm font-semibold text-slate-300 bg-slate-800 hover:bg-slate-700 rounded-xl transition-all border border-slate-700">Batal</button>
<button onclick="closeModal(true)" class="px-6 py-2.5 text-sm font-semibold text-slate-950 bg-gradient-to-r from-cyan-400 to-sky-400 hover:from-cyan-300 hover:to-sky-300 rounded-xl transition-all shadow-lg shadow-cyan-500/25">Mengerti</button>
</div>
</div>
</div>
<!-- Role Selection Screen -->
<div id="viewRoleSelector" class="max-w-3xl mx-auto my-12 text-center fade-in">
<div class="bg-slate-800/60 border border-slate-700/60 p-8 sm:p-12 rounded-3xl shadow-2xl backdrop-blur-xl relative overflow-hidden">
<div class="absolute -right-20 -top-20 w-60 h-60 bg-cyan-500/10 rounded-full blur-3xl pointer-events-none"></div>
<div class="absolute -left-20 -bottom-20 w-60 h-60 bg-indigo-500/10 rounded-full blur-3xl pointer-events-none"></div>
<span class="px-3.5 py-1.5 bg-cyan-500/10 text-cyan-400 font-bold text-xs uppercase tracking-widest rounded-full border border-cyan-500/20">Akses Terautentikasi</span>
<h2 class="text-3xl sm:text-4xl font-extrabold text-white mt-4 mb-3 tracking-tight">Pilih Portal Masuk</h2>
<p class="text-slate-400 mb-10 max-w-md mx-auto text-sm leading-relaxed">Silakan pilih akses sesuai peran Anda di departemen Informatika untuk mengelola atau memantau progres nilai.</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<!-- Teacher Portal Card -->
<button onclick="promptTeacherPassword()" class="group text-left p-6 sm:p-7 rounded-2xl border border-slate-700/80 hover:border-cyan-500 bg-slate-900/60 hover:bg-slate-900 transition-all duration-300 flex flex-col justify-between shadow-xl relative overflow-hidden">
<div class="absolute top-0 right-0 w-32 h-32 bg-cyan-500/5 rounded-full blur-2xl group-hover:bg-cyan-500/10 transition-colors"></div>
<div>
<div class="w-14 h-14 rounded-2xl bg-cyan-500/10 text-cyan-400 flex items-center justify-center text-2xl mb-5 group-hover:scale-110 group-hover:bg-cyan-500 group-hover:text-slate-950 transition-all duration-300 border border-cyan-500/20">
<i class="fa-solid fa-terminal"></i>
</div>
<h3 class="text-lg font-bold text-white mb-1.5 group-hover:text-cyan-400 transition-colors">Portal Guru / Pengajar</h3>
<p class="text-xs text-slate-400 leading-relaxed">Kelola bobot penilaian kode, nilai harian, sumatif modul, portofolio, catatan lab, serta impor Excel REKAP_NILAI. (Memerlukan Sandi)</p>
</div>
<div class="mt-8 flex items-center text-xs font-bold text-cyan-400 group-hover:translate-x-1.5 transition-transform">
Masuk Dashboard Guru <i class="fa-solid fa-arrow-right ml-2"></i>
</div>
</button>
<!-- Student Portal Card -->
<button onclick="switchPortal('student')" class="group text-left p-6 sm:p-7 rounded-2xl border border-slate-700/80 hover:border-sky-400 bg-slate-900/60 hover:bg-slate-900 transition-all duration-300 flex flex-col justify-between shadow-xl relative overflow-hidden">
<div class="absolute top-0 right-0 w-32 h-32 bg-sky-500/5 rounded-full blur-2xl group-hover:bg-sky-500/10 transition-colors"></div>
<div>
<div class="w-14 h-14 rounded-2xl bg-sky-500/10 text-sky-400 flex items-center justify-center text-2xl mb-5 group-hover:scale-110 group-hover:bg-sky-400 group-hover:text-slate-950 transition-all duration-300 border border-sky-500/20">
<i class="fa-solid fa-laptop-code"></i>
</div>
<h3 class="text-lg font-bold text-white mb-1.5 group-hover:text-sky-400 transition-colors">Portal Siswa / Praktikan</h3>
<p class="text-xs text-slate-400 leading-relaxed">Pantau transkrip nilai akhir praktikum, rincian komponen tugas, dan catatan pembimbing. (Tanpa Kata Sandi)</p>
</div>
<div class="mt-8 flex items-center text-xs font-bold text-sky-400 group-hover:translate-x-1.5 transition-transform">
Masuk Dashboard Siswa <i class="fa-solid fa-arrow-right ml-2"></i>
</div>
</button>
</div>
</div>
</div>
<div id="viewTeacherPortal" class="hidden fade-in space-y-6">
<div class="bg-slate-800/80 border border-slate-700/80 p-6 rounded-3xl shadow-xl backdrop-blur-md flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div>
<button onclick="backToSelector()" class="text-xs font-semibold text-slate-400 hover:text-cyan-400 mb-2 flex items-center gap-1.5 transition-colors">
<i class="fa-solid fa-arrow-left"></i> Kembali ke Pilih Portal
</button>
<h2 class="text-2xl font-extrabold text-white">Dashboard Manajemen Nilai Informatika</h2>
<p class="text-sm text-slate-400">Atur bobot rumus nilai, jenjang, kelas, jurusan IT, kontak siswa, & rekapitulasi nilai praktikum.</p>
</div>
<div class="flex flex-wrap items-center gap-3">
<!-- Excel Importer Input Button -->
<label class="px-4 py-2.5 bg-emerald-600 hover:bg-emerald-500 text-white font-bold text-xs rounded-xl shadow-lg shadow-emerald-600/20 transition-all flex items-center gap-2 cursor-pointer">
<i class="fa-solid fa-file-excel"></i> Impor Rekap Excel
<input type="file" id="excelFileInput" accept=".xlsx, .xls, .csv" class="hidden" onchange="handleExcelImport(event)">
</label>
<button onclick="saveAllTeacherData()" class="px-5 py-2.5 bg-gradient-to-r from-cyan-500 to-sky-500 hover:from-cyan-400 hover:to-sky-400 text-slate-950 font-bold text-sm rounded-xl shadow-lg shadow-cyan-500/20 transition-all flex items-center gap-2">
<i class="fa-solid fa-cloud-arrow-up"></i> Simpan Data ke Server
</button>
</div>
</div>
<!-- Configuration Box: Weights -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="bg-slate-800/80 border border-slate-700/80 p-6 rounded-3xl shadow-xl lg:col-span-1 space-y-4 backdrop-blur-md">
<h3 class="font-bold text-white flex items-center gap-2">
<i class="fa-solid fa-sliders text-cyan-400"></i> Konfigurasi Bobot Nilai (%)
</h3>
<p class="text-xs text-slate-400">Total akumulasi bobot komponen evaluasi harus bernilai tepat 100%.</p>
<div class="space-y-3.5 pt-2">
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">Nilai Harian / Tugas (NH)</label>
<input type="number" id="weightNH" value="30" onchange="validateWeights()" class="w-full px-3.5 py-2.5 bg-slate-900 border border-slate-700 rounded-xl text-sm text-slate-100 focus:ring-2 focus:ring-cyan-500 focus:outline-none">
</div>
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">Nilai Bulanan / Sumatif (NB)</label>
<input type="number" id="weightNB" value="25" onchange="validateWeights()" class="w-full px-3.5 py-2.5 bg-slate-900 border border-slate-700 rounded-xl text-sm text-slate-100 focus:ring-2 focus:ring-cyan-500 focus:outline-none">
</div>
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">Catatan / Sikap Lab (NC)</label>
<input type="number" id="weightNC" value="10" onchange="validateWeights()" class="w-full px-3.5 py-2.5 bg-slate-900 border border-slate-700 rounded-xl text-sm text-slate-100 focus:ring-2 focus:ring-cyan-500 focus:outline-none">
</div>
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">Projek / Coding Portofolio (NP)</label>
<input type="number" id="weightNP" value="35" onchange="validateWeights()" class="w-full px-3.5 py-2.5 bg-slate-900 border border-slate-700 rounded-xl text-sm text-slate-100 focus:ring-2 focus:ring-cyan-500 focus:outline-none">
</div>
<div class="pt-3 border-t border-slate-700/80 flex justify-between items-center text-sm font-bold">
<span class="text-slate-300">Total Akumulasi:</span>
<span id="weightTotalDisplay" class="text-cyan-400">100%</span>
</div>
</div>
</div>
<!-- Student Grades Input Table container -->
<div class="bg-slate-800/80 border border-slate-700/80 p-6 rounded-3xl shadow-xl lg:col-span-2 space-y-4 backdrop-blur-md">
<div class="flex justify-between items-center flex-wrap gap-4">
<div>
<h3 class="font-bold text-white flex items-center gap-2">
<i class="fa-solid fa-database text-cyan-400"></i> Rekapitulasi Data Siswa & Nilai Praktikum
</h3>
<p class="text-xs text-slate-400">Kelola dan update data nilai siswa secara real-time.</p>
</div>
<button onclick="openAddStudentModal()" class="px-4 py-2 bg-cyan-500/10 hover:bg-cyan-500/20 text-cyan-400 border border-cyan-500/30 font-bold text-xs rounded-xl transition-colors flex items-center gap-1.5 shadow-sm">
<i class="fa-solid fa-user-plus"></i> Tambah Siswa Baru
</button>
</div>
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-slate-700 text-xs font-bold text-slate-400 uppercase tracking-wider">
<th class="py-3 px-3">Siswa & Kontak</th>
<th class="py-3 px-3">Jenjang, Kelas & Jurusan</th>
<th class="py-3 px-2 text-center">NH (30%)</th>
<th class="py-3 px-2 text-center">NB (25%)</th>
<th class="py-3 px-2 text-center">NC (10%)</th>
<th class="py-3 px-2 text-center">NP (35%)</th>
<th class="py-3 px-2 text-center">NA</th>
<th class="py-3 px-3 text-right">Aksi</th>
</tr>
</thead>
<tbody id="teacherTableBody" class="divide-y divide-slate-700/60 text-sm">
<!-- Dynamic rows populated via JS -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<div id="viewStudentPortal" class="hidden fade-in space-y-6">
<div class="bg-slate-800/80 border border-slate-700/80 p-6 rounded-3xl shadow-xl backdrop-blur-md flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div>
<button onclick="backToSelector()" class="text-xs font-semibold text-slate-400 hover:text-sky-400 mb-2 flex items-center gap-1.5 transition-colors">
<i class="fa-solid fa-arrow-left"></i> Kembali ke Pilih Portal
</button>
<h2 class="text-2xl font-extrabold text-white">Transkrip Portal Nilai Siswa</h2>
<p class="text-sm text-slate-400">Pilih nama Anda untuk melihat rekap nilai akhir, rincian komponen, dan evaluasi lab.</p>
</div>
<div class="w-full sm:w-auto">
<label class="block text-xs font-semibold text-slate-300 mb-1">Pilih Identitas Siswa:</label>
<select id="studentSelectorDropdown" onchange="renderStudentDashboard()" class="w-full sm:w-72 px-3.5 py-2.5 border border-slate-700 rounded-xl text-sm font-semibold bg-slate-900 text-slate-100 focus:ring-2 focus:ring-sky-400 focus:outline-none">
<!-- Populated dynamically -->
</select>
</div>
</div>
<!-- Student Detailed Report Card -->
<div id="studentReportCard" class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Summary Card -->
<div class="bg-gradient-to-br from-cyan-600 via-sky-600 to-indigo-700 text-white p-6 rounded-3xl shadow-2xl flex flex-col justify-between relative overflow-hidden">
<div class="absolute -right-10 -bottom-10 w-40 h-40 bg-white/10 rounded-full blur-xl pointer-events-none"></div>
<div>
<span class="px-3 py-1 bg-white/20 text-white font-bold text-xs rounded-full uppercase tracking-wider backdrop-blur-md">Hasil Akhir Rapor IT</span>
<h3 id="srStudentName" class="text-2xl font-extrabold mt-4 mb-1">Nama Siswa</h3>
<p id="srStudentLevelClassMajor" class="text-xs text-sky-100 mb-2 font-medium">Jenjang, Kelas, Jurusan & Telepon</p>
</div>
<div class="my-6 text-center bg-slate-950/20 backdrop-blur-md py-6 rounded-2xl border border-white/15 shadow-inner">
<span class="text-xs uppercase tracking-widest text-sky-200 font-semibold block mb-1">Nilai Akhir (NA)</span>
<span id="srFinalScore" class="text-6xl font-black tracking-tight text-white">0.0</span>
</div>
<div class="text-xs text-sky-100 flex justify-between items-center bg-slate-950/20 px-4 py-2.5 rounded-xl border border-white/10">
<span>Predikat: <strong id="srGradePredicate" class="text-white font-extrabold text-sm ml-1">A</strong></span>
<span id="srStatusPass" class="px-2.5 py-1 bg-emerald-500/80 rounded-lg text-[10px] font-bold text-white shadow">Tuntas</span>
</div>
</div>
<!-- Breakdown Components -->
<div class="bg-slate-800/80 border border-slate-700/80 p-6 rounded-3xl shadow-xl md:col-span-2 space-y-4 backdrop-blur-md">
<h3 class="font-bold text-white flex items-center gap-2">
<i class="fa-solid fa-chart-pie text-sky-400"></i> Rincian Formula & Komponen Nilai
</h3>
<p class="text-xs text-slate-400">Komposisi perhitungan nilai akhir berdasarkan standar laboratorium informatika.</p>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 pt-2">
<div class="p-4 rounded-2xl bg-slate-900 border border-slate-700/80 flex justify-between items-center shadow-inner">
<div>
<span class="text-xs font-semibold text-slate-400 block mb-0.5">Nilai Harian (NH)</span>
<span id="srValNH" class="text-xl font-bold text-white">0</span>
<span class="text-[10px] text-slate-500 block">Bobot 30%</span>
</div>
<div class="text-right">
<span class="text-xs text-slate-500 block">Kontribusi</span>
<span id="srContribNH" class="text-sm font-bold text-cyan-400">0</span>
</div>
</div>
<div class="p-4 rounded-2xl bg-slate-900 border border-slate-700/80 flex justify-between items-center shadow-inner">
<div>
<span class="text-xs font-semibold text-slate-400 block mb-0.5">Nilai Bulanan (NB)</span>
<span id="srValNB" class="text-xl font-bold text-white">0</span>
<span class="text-[10px] text-slate-500 block">Bobot 25%</span>
</div>
<div class="text-right">
<span class="text-xs text-slate-500 block">Kontribusi</span>
<span id="srContribNB" class="text-sm font-bold text-cyan-400">0</span>
</div>
</div>
<div class="p-4 rounded-2xl bg-slate-900 border border-slate-700/80 flex justify-between items-center shadow-inner">
<div>
<span class="text-xs font-semibold text-slate-400 block mb-0.5">Catatan / Sikap Lab (NC)</span>
<span id="srValNC" class="text-xl font-bold text-white">0</span>
<span class="text-[10px] text-slate-500 block">Bobot 10%</span>
</div>
<div class="text-right">
<span class="text-xs text-slate-500 block">Kontribusi</span>
<span id="srContribNC" class="text-sm font-bold text-cyan-400">0</span>
</div>
</div>
<div class="p-4 rounded-2xl bg-slate-900 border border-slate-700/80 flex justify-between items-center shadow-inner">
<div>
<span class="text-xs font-semibold text-slate-400 block mb-0.5">Projek & Portofolio (NP)</span>
<span id="srValNP" class="text-xl font-bold text-white">0</span>
<span class="text-[10px] text-slate-500 block">Bobot 35%</span>
</div>
<div class="text-right">
<span class="text-xs text-slate-500 block">Kontribusi</span>
<span id="srContribNP" class="text-sm font-bold text-cyan-400">0</span>
</div>
</div>
</div>
<!-- Teacher Note / Journal Note -->
<div class="mt-4 p-4 rounded-2xl bg-cyan-950/40 border border-cyan-500/30">
<h4 class="text-xs font-bold text-cyan-400 uppercase tracking-wider mb-1.5 flex items-center gap-2">
<i class="fa-solid fa-terminal text-cyan-400"></i> Catatan Pembimbing & Evaluasi Kode
</h4>
<p id="srTeacherNote" class="text-sm text-slate-300 italic">"Menunjukkan pemahaman algoritma dan logika pemrograman yang sangat baik."</p>
</div>
</div>
</div>
</div>
</main>
<!-- Add/Edit Student Modal -->
<div id="studentModal" class="fixed inset-0 bg-slate-950/80 backdrop-blur-md z-50 hidden flex items-center justify-center p-4">
<div class="bg-slate-900 border border-slate-800 rounded-3xl max-w-lg w-full p-6 sm:p-8 shadow-2xl fade-in text-slate-100 max-h-[90vh] overflow-y-auto">
<h3 id="studentModalTitle" class="text-xl font-bold text-white mb-5 flex items-center gap-2.5">
<i class="fa-solid fa-user-pen text-cyan-400"></i> Tambah Siswa Baru
</h3>
<input type="hidden" id="editStudentId">
<div class="space-y-4">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">Nama Lengkap Siswa</label>
<input type="text" id="inputStudentName" placeholder="Contoh: Budi Santoso" class="w-full px-3.5 py-2.5 bg-slate-950 border border-slate-700 rounded-xl text-sm text-slate-100 focus:ring-2 focus:ring-cyan-500 focus:outline-none">
</div>
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">Jenjang Pendidikan</label>
<select id="inputStudentLevel" onchange="updateModalMajorOptions()" class="w-full px-3.5 py-2.5 bg-slate-950 border border-slate-700 rounded-xl text-sm text-slate-100 focus:ring-2 focus:ring-cyan-500 focus:outline-none">
<option value="SMP">SMP</option>
<option value="SMK" selected>SMK</option>
</select>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">Kelas</label>
<select id="inputStudentGradeClass" class="w-full px-3.5 py-2.5 bg-slate-950 border border-slate-700 rounded-xl text-sm text-slate-100 focus:ring-2 focus:ring-cyan-500 focus:outline-none">
<!-- Populated dynamically based on level -->
</select>
</div>
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">Jurusan / Konsentrasi Keahlian</label>
<select id="inputStudentMajor" class="w-full px-3.5 py-2.5 bg-slate-950 border border-slate-700 rounded-xl text-sm text-slate-100 focus:ring-2 focus:ring-cyan-500 focus:outline-none">
<!-- Populated dynamically based on level -->
</select>
</div>
</div>
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">Nomor Telepon / WhatsApp</label>
<input type="text" id="inputStudentPhone" placeholder="Contoh: 081234567890" class="w-full px-3.5 py-2.5 bg-slate-950 border border-slate-700 rounded-xl text-sm text-slate-100 focus:ring-2 focus:ring-cyan-500 focus:outline-none">
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">Nilai Harian (0-100)</label>
<input type="number" id="inputNH" value="85" class="w-full px-3.5 py-2.5 bg-slate-950 border border-slate-700 rounded-xl text-sm text-slate-100 focus:ring-2 focus:ring-cyan-500 focus:outline-none">
</div>
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">Nilai Bulanan (0-100)</label>
<input type="number" id="inputNB" value="80" class="w-full px-3.5 py-2.5 bg-slate-950 border border-slate-700 rounded-xl text-sm text-slate-100 focus:ring-2 focus:ring-cyan-500 focus:outline-none">
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">Catatan/Sikap (0-100)</label>
<input type="number" id="inputNC" value="90" class="w-full px-3.5 py-2.5 bg-slate-950 border border-slate-700 rounded-xl text-sm text-slate-100 focus:ring-2 focus:ring-cyan-500 focus:outline-none">
</div>
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">Nilai Projek (0-100)</label>
<input type="number" id="inputNP" value="88" class="w-full px-3.5 py-2.5 bg-slate-950 border border-slate-700 rounded-xl text-sm text-slate-100 focus:ring-2 focus:ring-cyan-500 focus:outline-none">
</div>
</div>
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">Catatan Khusus Pembimbing Lab</label>
<textarea id="inputNote" rows="2" class="w-full px-3.5 py-2.5 bg-slate-950 border border-slate-700 rounded-xl text-sm text-slate-100 focus:ring-2 focus:ring-cyan-500 focus:outline-none" placeholder="Tulis catatan perkembangan siswa..."></textarea>
</div>
</div>
<div class="flex justify-end gap-3 mt-6">
<button onclick="closeStudentModal()" class="px-5 py-2.5 text-sm font-semibold text-slate-300 bg-slate-800 hover:bg-slate-700 rounded-xl transition-all border border-slate-700">Batal</button>
<button onclick="saveStudentData()" class="px-6 py-2.5 text-sm font-semibold text-slate-950 bg-gradient-to-r from-cyan-400 to-sky-400 hover:from-cyan-300 hover:to-sky-300 rounded-xl transition-all shadow-lg shadow-cyan-500/20">Simpan Siswa</button>
</div>
</div>
</div>
<!-- Teacher Password Authentication Modal -->
<div id="teacherPasswordModal" class="fixed inset-0 bg-slate-950/80 backdrop-blur-md z-50 hidden flex items-center justify-center p-4">
<div class="bg-slate-900 border border-slate-800 rounded-3xl max-w-sm w-full p-6 sm:p-8 shadow-2xl fade-in text-slate-100">
<div class="w-14 h-14 rounded-2xl bg-cyan-500/10 text-cyan-400 flex items-center justify-center text-2xl mb-5 border border-cyan-500/20 shadow-lg shadow-cyan-500/10">
<i class="fa-solid fa-lock"></i>
</div>
<h3 class="text-xl font-bold text-white mb-2">Autentikasi Guru</h3>
<p class="text-xs text-slate-400 mb-6 leading-relaxed">Silakan masukkan kode sandi pengajar (<code class="text-cyan-400 font-bold">suryogarage</code>) untuk masuk ke Portal Guru.</p>
<div class="space-y-4">
<div>
<label class="block text-xs font-semibold text-slate-300 mb-1">Kata Sandi Pengajar</label>
<input type="password" id="inputTeacherPassword" placeholder="Masukkan sandi..." class="w-full px-3.5 py-2.5 bg-slate-950 border border-slate-700 rounded-xl text-sm text-slate-100 focus:ring-2 focus:ring-cyan-500 focus:outline-none" onkeydown="if(event.key==='Enter') verifyTeacherPassword()">
</div>
<div id="passwordErrorMsg" class="text-xs text-rose-400 hidden font-semibold">Kata sandi salah. Silakan coba lagi.</div>
</div>
<div class="flex justify-end gap-3 mt-6">
<button onclick="closeTeacherPasswordModal()" class="px-4 py-2.5 text-xs font-semibold text-slate-300 bg-slate-800 hover:bg-slate-700 rounded-xl transition-all border border-slate-700">Batal</button>
<button onclick="verifyTeacherPassword()" class="px-5 py-2.5 text-xs font-semibold text-slate-950 bg-gradient-to-r from-cyan-400 to-sky-400 hover:from-cyan-300 hover:to-sky-300 rounded-xl transition-all shadow-lg shadow-cyan-500/20">Masuk</button>
</div>
</div>
</div>
<footer class="bg-slate-950 border-t border-slate-800/80 py-6 text-center text-xs text-slate-500 mt-auto">
<p>© 2026 Portal Nilai Informatika — Professional IT Grading & Assessment System.</p>
</footer>
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
import { getFirestore, doc, setDoc, getDoc, collection, getDocs, onSnapshot } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
// Environment variables setup
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-grade-app';
const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : null;
let db = null;
let auth = null;
let userId = 'offline-user';
let isCloudActive = false;
// Default In-Memory / State Data populated from image list
window.appState = {
weights: { nh: 30, nb: 25, nc: 10, np: 35 },
students: [
{ id: 'img_1', name: 'ARIE SISW', level: 'SMK', gradeClass: 'Kelas 10', major: 'AK', phone: '081200000001', nh: 65.5, nb: 70, nc: 80, np: 75, note: 'Perlu bimbingan ekstra pada tugas praktik dasar.' },
{ id: 'img_2', name: 'AUDY TYA', level: 'SMK', gradeClass: 'Kelas 10', major: 'AK', phone: '081200000002', nh: 78, nb: 80, nc: 85, np: 80, note: 'Keaktifan dan pemahaman materi cukup baik.' },
{ id: 'img_3', name: 'Aura Nabi', level: 'SMK', gradeClass: 'Kelas 10', major: 'AK', phone: '081200000003', nh: 61, nb: 65, nc: 75, np: 70, note: 'Tingkatkan latihan mandiri di rumah.' },
{ id: 'img_4', name: 'AZHRIEL F', level: 'SMK', gradeClass: 'Kelas 10', major: 'AK', phone: '081200000004', nh: 74.5, nb: 75, nc: 80, np: 78, note: 'Progres stabil dalam pengerjaan tugas.' },
{ id: 'img_5', name: 'CHAIRIL G', level: 'SMK', gradeClass: 'Kelas 10', major: 'AK', phone: '081200000005', nh: 77.5, nb: 78, nc: 82, np: 80, note: 'Pemahaman logika dasar sudah baik.' },
{ id: 'img_6', name: 'JAHIRA PU', level: 'SMK', gradeClass: 'Kelas 10', major: 'AK', phone: '081200000006', nh: 0, nb: 50, nc: 60, np: 50, note: 'Perlu konfirmasi kehadiran dan pengumpulan tugas susulan.' },
{ id: 'img_7', name: 'MOHAMM', level: 'SMK', gradeClass: 'Kelas 10', major: 'AK', phone: '081200000007', nh: 83.5, nb: 85, nc: 88, np: 85, note: 'Sangat teliti dalam menyelesaikan asesmen.' },
{ id: 'img_8', name: 'MUHAMA', level: 'SMK', gradeClass: 'Kelas 10', major: 'AK', phone: '081200000008', nh: 80, nb: 80, nc: 85, np: 82, note: 'Konsisten menyelesaikan latihan harian.' },
{ id: 'img_9', name: 'MUHAMM', level: 'SMK', gradeClass: 'Kelas 10', major: 'AK', phone: '081200000009', nh: 56, nb: 60, nc: 70, np: 65, note: 'Perlu perhatian khusus pada materi praktikum.' },
{ id: 'img_10', name: 'MUHAMM', level: 'SMK', gradeClass: 'Kelas 10', major: 'AK', phone: '081200000010', nh: 81.5, nb: 82, nc: 85, np: 84, note: 'Menunjukkan keaktifan dalam diskusi kelompok.' },
{ id: 'img_11', name: 'MUHAMM', level: 'SMK', gradeClass: 'Kelas 10', major: 'AK', phone: '081200000011', nh: 54, nb: 60, nc: 65, np: 60, note: 'Perlu peningkatan kedisiplinan dan pengumpulan tugas.' },
{ id: 'img_12', name: 'Revi Rizky', level: 'SMK', gradeClass: 'Kelas 10', major: 'AK', phone: '081200000012', nh: 83.5, nb: 85, nc: 90, np: 86, note: 'Sikap dan hasil kerja lab memuaskan.' },
{ id: 'img_13', name: 'SAIF AMAI', level: 'SMK', gradeClass: 'Kelas 10', major: 'AK', phone: '081200000013', nh: 63.25, nb: 68, nc: 75, np: 70, note: 'Perlu bimbingan lanjutan pada latihan soal.' },
{ id: 'img_14', name: 'SASKIA FA', level: 'SMK', gradeClass: 'Kelas 10', major: 'AK', phone: '081200000014', nh: 82, nb: 83, nc: 88, np: 85, note: 'Ketelitian tugas praktik sangat baik.' },
{ id: 'img_15', name: 'ZAHRA RIN', level: 'SMK', gradeClass: 'Kelas 10', major: 'AK', phone: '081200000015', nh: 75.5, nb: 78, nc: 82, np: 80, note: 'Performa stabil sepanjang semester.' },
{ id: 'img_16', name: 'AQIL FATT', level: 'SMK', gradeClass: 'Kelas 10', major: 'AV', phone: '081200000016', nh: 79.5, nb: 80, nc: 85, np: 82, note: 'Pemahaman materi audio video baik.' },
{ id: 'img_17', name: 'DIMAS JOI', level: 'SMK', gradeClass: 'Kelas 10', major: 'AV', phone: '081200000017', nh: 83.5, nb: 85, nc: 88, np: 86, note: 'Keterampilan praktik lab sangat baik.' },
{ id: 'img_18', name: 'EGA ERNE', level: 'SMK', gradeClass: 'Kelas 10', major: 'AV', phone: '081200000018', nh: 83, nb: 84, nc: 86, np: 85, note: 'Konsisten menyelesaikan tugas tepat waktu.' },
{ id: 'img_19', name: 'FAKHRY TR', level: 'SMK', gradeClass: 'Kelas 10', major: 'AV', phone: '081200000019', nh: 85, nb: 86, nc: 90, np: 88, note: 'Sangat antusias dalam praktikum hardware/software.' },
{ id: 'img_20', name: 'HAMZAH A', level: 'SMK', gradeClass: 'Kelas 10', major: 'AV', phone: '081200000020', nh: 73.5, nb: 75, nc: 80, np: 78, note: 'Cukup baik, pertahankan motivasi belajar.' },
{ id: 'img_21', name: 'MUHAMA', level: 'SMK', gradeClass: 'Kelas 10', major: 'AV', phone: '081200000021', nh: 81.5, nb: 82, nc: 85, np: 84, note: 'Aktif bertanya saat materi praktik.' },
{ id: 'img_22', name: 'MUHAMAMA', level: 'SMK', gradeClass: 'Kelas 10', major: 'AV', phone: '081200000022', nh: 53.5, nb: 60, nc: 65, np: 60, note: 'Perlu pendampingan khusus remedial.' },
{ id: 'img_23', name: 'MUHAMA', level: 'SMK', gradeClass: 'Kelas 10', major: 'AV', phone: '081200000023', nh: 75.5, nb: 78, nc: 82, np: 80, note: 'Peningkatan nilai terlihat konsisten.' },
{ id: 'img_24', name: 'RADITYA A', level: 'SMK', gradeClass: 'Kelas 10', major: 'AV', phone: '081200000024', nh: 85, nb: 85, nc: 90, np: 88, note: 'Hasil kerja portofolio sangat rapi.' },
{ id: 'img_25', name: 'RAFFI ABIY', level: 'SMK', gradeClass: 'Kelas 10', major: 'AV', phone: '081200000025', nh: 62.5, nb: 65, nc: 75, np: 70, note: 'Perlu peningkatan fokus dalam sesi teori.' },
{ id: 'img_26', name: 'RIFQI SYAI', level: 'SMK', gradeClass: 'Kelas 10', major: 'AV', phone: '081200000026', nh: 76.5, nb: 78, nc: 82, np: 80, note: 'Disiplin dan kehadiran lab sangat baik.' },
{ id: 'img_27', name: 'RINALDI', level: 'SMK', gradeClass: 'Kelas 10', major: 'AV', phone: '081200000027', nh: 77, nb: 78, nc: 82, np: 80, note: 'Kerjasama tim dalam kelompok baik.' },
{ id: 'img_28', name: 'RIZKY MAU', level: 'SMK', gradeClass: 'Kelas 10', major: 'AV', phone: '081200000028', nh: 82, nb: 83, nc: 87, np: 85, note: 'Pemahaman teknis memuaskan.' },
{ id: 'img_29', name: 'VICKO AL-', level: 'SMK', gradeClass: 'Kelas 10', major: 'AV', phone: '081200000029', nh: 73, nb: 75, nc: 80, np: 78, note: 'Tingkatkan ketelitian saat uji kompetensi.' },
{ id: 'img_30', name: 'ZHIDANE H', level: 'SMK', gradeClass: 'Kelas 10', major: 'AV', phone: '081200000030', nh: 82, nb: 84, nc: 88, np: 85, note: 'Menunjukkan kemampuan analisis yang baik.' },
{ id: 'img_31', name: 'ADITYA PU', level: 'SMK', gradeClass: 'Kelas 10', major: 'MP', phone: '081200000031', nh: 68.5, nb: 70, nc: 78, np: 74, note: 'Cukup aktif dalam manajemen perkantoran digital.' },
{ id: 'img_32', name: 'AHMAD R', level: 'SMK', gradeClass: 'Kelas 10', major: 'MP', phone: '081200000032', nh: 68, nb: 70, nc: 78, np: 74, note: 'Perlu latihan lebih sering pada aplikasi perkantoran.' },
{ id: 'img_33', name: 'ANDIRA M', level: 'SMK', gradeClass: 'Kelas 10', major: 'MP', phone: '081200000033', nh: 83, nb: 85, nc: 90, np: 87, note: 'Kerapian administrasi dan tugas sangat baik.' },
{ id: 'img_34', name: 'ARRAHMA', level: 'SMK', gradeClass: 'Kelas 10', major: 'MP', phone: '081200000034', nh: 92.5, nb: 94, nc: 96, np: 94, note: 'Prestasi sangat menonjol di kelas.' },
{ id: 'img_35', name: 'CLARISA P', level: 'SMK', gradeClass: 'Kelas 10', major: 'MP', phone: '081200000035', nh: 82.5, nb: 85, nc: 88, np: 86, note: 'Ketepatan pengumpulan tugas sangat baik.' },
{ id: 'img_36', name: 'FADILLAH', level: 'SMK', gradeClass: 'Kelas 10', major: 'MP', phone: '081200000036', nh: 85, nb: 86, nc: 90, np: 88, note: 'Sangat responsif terhadap materi instruktur.' },
{ id: 'img_37', name: 'HILMI ARI', level: 'SMK', gradeClass: 'Kelas 10', major: 'MP', phone: '081200000037', nh: 79, nb: 80, nc: 85, np: 82, note: 'Keaktifan kelas memuaskan.' },
{ id: 'img_38', name: 'KESYA SEL', level: 'SMK', gradeClass: 'Kelas 10', major: 'MP', phone: '081200000038', nh: 81, nb: 82, nc: 86, np: 84, note: 'Konsisten menjaga kualitas tugas.' },
{ id: 'img_39', name: 'MUHAMA', level: 'SMK', gradeClass: 'Kelas 10', major: 'MP', phone: '081200000039', nh: 83, nb: 84, nc: 88, np: 86, note: 'Kerjasama kelompok sangat baik.' },
{ id: 'img_40', name: 'MUHAMAF', level: 'SMK', gradeClass: 'Kelas 10', major: 'MP', phone: '081200000040', nh: 64, nb: 68, nc: 75, np: 70, note: 'Perlu peningkatan fokus pengerjaan tugas.' },
{ id: 'img_41', name: 'Muhammad', level: 'SMK', gradeClass: 'Kelas 10', major: 'MP', phone: '081200000041', nh: 84, nb: 85, nc: 90, np: 87, note: 'Hasil kerja praktik sangat rapi.' },
{ id: 'img_42', name: 'MURNI CA', level: 'SMK', gradeClass: 'Kelas 10', major: 'MP', phone: '081200000042', nh: 83, nb: 84, nc: 88, np: 86, note: 'Ketelitian administrasi perkantoran baik.' },
{ id: 'img_43', name: 'NAZARYAN', level: 'SMK', gradeClass: 'Kelas 10', major: 'MP', phone: '081200000043', nh: 85, nb: 86, nc: 90, np: 88, note: 'Sangat aktif berdiskusi.' },
{ id: 'img_44', name: 'NURKHOLI', level: 'SMK', gradeClass: 'Kelas 10', major: 'MP', phone: '081200000044', nh: 85, nb: 86, nc: 90, np: 88, note: 'Disiplin dan dedikasi belajar tinggi.' },
{ id: 'img_45', name: 'QATRUNN', level: 'SMK', gradeClass: 'Kelas 10', major: 'MP', phone: '081200000045', nh: 80.5, nb: 82, nc: 85, np: 83, note: 'Pemahaman modul perkantoran memuaskan.' },
{ id: 'img_46', name: 'Ahmad Ok', level: 'SMK', gradeClass: 'Kelas 10', major: 'TKR', phone: '081200000046', nh: 83, nb: 84, nc: 88, np: 86, note: 'Praktik bengkel dan kelistrikan baik.' },
{ id: 'img_47', name: 'Angga Juli', level: 'SMK', gradeClass: 'Kelas 10', major: 'TKR', phone: '081200000047', nh: 85, nb: 86, nc: 90, np: 88, note: 'Sangat terampil dalam praktik otomotif dasar.' },
{ id: 'img_48', name: 'Bintang W', level: 'SMK', gradeClass: 'Kelas 10', major: 'TKR', phone: '081200000048', nh: 85, nb: 86, nc: 90, np: 88, note: 'Disiplin bengkel sangat tinggi.' },
{ id: 'img_49', name: 'DASWA N', level: 'SMK', gradeClass: 'Kelas 10', major: 'TKR', phone: '081200000049', nh: 83, nb: 84, nc: 88, np: 86, note: 'Kerjasama tim mekanik sangat baik.' },
{ id: 'img_50', name: 'DUSTIN GI', level: 'SMK', gradeClass: 'Kelas 10', major: 'TKR', phone: '081200000050', nh: 85, nb: 86, nc: 90, np: 88, note: 'Pemahaman komponen mesin sangat teliti.' },
{ id: 'img_51', name: 'FAUJAN SA', level: 'SMK', gradeClass: 'Kelas 10', major: 'TKR', phone: '081200000051', nh: 77, nb: 79, nc: 83, np: 80, note: 'Cukup aktif dalam latihan praktikum.' },
{ id: 'img_52', name: 'HILMI PUT', level: 'SMK', gradeClass: 'Kelas 10', major: 'TKR', phone: '081200000052', nh: 79, nb: 80, nc: 85, np: 82, note: 'Konsisten mengikuti instruksi keselamatan kerja.' },
{ id: 'img_53', name: 'HISAM A', level: 'SMK', gradeClass: 'Kelas 10', major: 'TKR', phone: '081200000053', nh: 78, nb: 80, nc: 84, np: 81, note: 'Ketepatan waktu kumpul laporan baik.' },
{ id: 'img_54', name: 'MOHAMN', level: 'SMK', gradeClass: 'Kelas 10', major: 'TKR', phone: '081200000054', nh: 85, nb: 86, nc: 90, np: 88, note: 'Keterampilan motorik dan analisis mesin bagus.' },
{ id: 'img_55', name: 'Muhamad', level: 'SMK', gradeClass: 'Kelas 10', major: 'TKR', phone: '081200000055', nh: 80, nb: 81, nc: 85, np: 83, note: 'Keaktifan dan antusiasme tinggi.' },
{ id: 'img_56', name: 'MUHAMM', level: 'SMK', gradeClass: 'Kelas 10', major: 'TKR', phone: '081200000056', nh: 79, nb: 80, nc: 85, np: 82, note: 'Pemahaman materi dasar mesin baik.' },
{ id: 'img_57', name: 'NAUVAL D', level: 'SMK', gradeClass: 'Kelas 10', major: 'TKR', phone: '081200000057', nh: 85, nb: 86, nc: 90, np: 88, note: 'Sangat cekatan dalam pembongkaran komponen.' },
{ id: 'img_58', name: 'RIZKI RAM', level: 'SMK', gradeClass: 'Kelas 10', major: 'TKR', phone: '081200000058', nh: 85, nb: 86, nc: 90, np: 88, note: 'Hasil kerja praktik mesin memuaskan.' },
{ id: 'img_59', name: 'SUSENO A', level: 'SMK', gradeClass: 'Kelas 10', major: 'TKR', phone: '081200000059', nh: 83, nb: 84, nc: 88, np: 86, note: 'Disiplin dan kepatuhan SOP sangat baik.' },
{ id: 'img_60', name: 'SYAHID M', level: 'SMK', gradeClass: 'Kelas 10', major: 'TKR', phone: '081200000060', nh: 85, nb: 86, nc: 90, np: 88, note: 'Prestasi dan kinerja praktik konsisten.' }
]
};
// Initialize Firebase if config exists
if (firebaseConfig) {
try {
const app = initializeApp(firebaseConfig);
db = getFirestore(app);
auth = getAuth(app);
const token = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;
if (token) {
signInWithCustomToken(auth, token).then(() => {
userId = auth.currentUser?.uid || crypto.randomUUID();
isCloudActive = true;
initCloudSync();
}).catch(() => {
signInAnonymously(auth).then(() => {
userId = auth.currentUser?.uid || crypto.randomUUID();
isCloudActive = true;
initCloudSync();
});
});
} else {
signInAnonymously(auth).then(() => {
userId = auth.currentUser?.uid || crypto.randomUUID();
isCloudActive = true;
initCloudSync();
});
}
} catch (e) {
console.log("Running in offline mode.");
}
}
function initCloudSync() {
if (!db || !userId) return;
const docRef = doc(db, 'artifacts', appId, 'public', 'data', 'informatika_grading_config');
onSnapshot(docRef, (docSnap) => {
if (docSnap.exists()) {
const data = docSnap.data();
if (data && data.weights && data.students) {
window.appState.weights = data.weights;
window.appState.students = data.students;
refreshUIViews();
}
}
}, (err) => { console.error(err); });
}
window.saveAllTeacherData = async function() {
const wNh = parseFloat(document.getElementById('weightNH').value) || 0;
const wNb = parseFloat(document.getElementById('weightNB').value) || 0;
const wNc = parseFloat(document.getElementById('weightNC').value) || 0;
const wNp = parseFloat(document.getElementById('weightNP').value) || 0;
if ((wNh + wNb + wNc + wNp) !== 100) {
showModal("Peringatan Bobot", "Total bobot seluruh komponen wajib persis 100% sebelum disimpan.", "warning");
return;
}
window.appState.weights = { nh: wNh, nb: wNb, nc: wNc, np: wNp };
if (isCloudActive && db) {
try {
const docRef = doc(db, 'artifacts', appId, 'public', 'data', 'informatika_grading_config');
await setDoc(docRef, window.appState);
showModal("Berhasil Disimpan", "Data bobot dan nilai siswa berhasil disinkronkan ke server cloud.", "success");
} catch (e) {
showModal("Penyimpanan Lokal", "Data berhasil diperbarui di memori lokal peramban.", "success");
}
} else {
showModal("Penyimpanan Lokal", "Data berhasil diperbarui secara lokal.", "success");
}
refreshUIViews();
};
// Excel File Importer Handler using SheetJS
window.handleExcelImport = function(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
// Take the first worksheet
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
// Convert sheet to JSON rows
const rows = XLSX.utils.sheet_to_json(worksheet, { defval: '' });
if (!rows || rows.length === 0) {
showModal("Gagal Impor", "File Excel kosong atau format tabel tidak dikenali.", "warning");
return;
}
let importedCount = 0;
rows.forEach((row, idx) => {
// Flexible key matching for columns like Nama, Nilai Harian, NH, NB, etc.
const name = row['Nama'] || row['Nama Siswa'] || row['NAMA'] || row['nama'] || row['Student Name'] || '';
if (!name) return;
const level = row['Jenjang'] || row['Level'] || row['JENJANG'] || 'SMK';
const gradeClass = row['Kelas'] || row['Grade'] || row['KELAS'] || 'Kelas 10';
const major = row['Jurusan'] || row['Major'] || row['JURUSAN'] || 'PPLG';
const phone = row['Telepon'] || row['HP'] || row['No HP'] || row['WhatsApp'] || '081200000000';
const nh = parseFloat(row['NH'] || row['Nilai Harian'] || row['Harian'] || 80) || 80;
const nb = parseFloat(row['NB'] || row['Nilai Bulanan'] || row['Bulanan'] || row['Sumatif'] || 80) || 80;
const nc = parseFloat(row['NC'] || row['Catatan'] || row['Sikap'] || 85) || 85;
const np = parseFloat(row['NP'] || row['Nilai Projek'] || row['Projek'] || 85) || 85;
const note = row['Catatan Guru'] || row['Keterangan'] || 'Diimpor dari berkas REKAP_NILAI Excel.';
const newStudent = {
id: 'imp_' + Date.now() + '_' + idx,
name: String(name).trim(),
level: String(level).trim(),
gradeClass: String(gradeClass).trim(),
major: String(major).trim(),
phone: String(phone).trim(),
nh, nb, nc, np,
note: String(note).trim()
};
window.appState.students.push(newStudent);
importedCount++;
});
populateTeacherTable();
showModal("Berhasil Impor Excel", `Berhasil memuat ${importedCount} data siswa dari file ${file.name}.`, "success");
} catch (err) {
console.error(err);
showModal("Kesalahan Berkas", "Terjadi kesalahan saat memproses file Excel. Pastikan format tabel benar.", "warning");
}
// Reset file input
event.target.value = '';
};
reader.readAsArrayBuffer(file);
};
// Teacher Password Verification Handlers
window.promptTeacherPassword = function() {
document.getElementById('inputTeacherPassword').value = '';
document.getElementById('passwordErrorMsg').classList.add('hidden');
document.getElementById('teacherPasswordModal').classList.remove('hidden');
setTimeout(() => {
document.getElementById('inputTeacherPassword').focus();
}, 100);
};
window.closeTeacherPasswordModal = function() {
document.getElementById('teacherPasswordModal').classList.add('hidden');
};
window.verifyTeacherPassword = function() {
const pwd = document.getElementById('inputTeacherPassword').value;
if (pwd === 'suryogarage') {
closeTeacherPasswordModal();
switchPortal('teacher');
} else {
document.getElementById('passwordErrorMsg').classList.remove('hidden');
}
};
// UI View Switcher Functions
window.switchPortal = function(role) {
document.getElementById('viewRoleSelector').classList.add('hidden');
const roleText = document.getElementById('currentRoleText');
// Update Navigation Pill Active Styles
const btnT = document.getElementById('navBtnTeacher');
const btnS = document.getElementById('navBtnStudent');
const btnSel = document.getElementById('navBtnSelector');
btnT.className = "px-3.5 py-1.5 rounded-xl text-xs font-bold transition-all flex items-center gap-2 text-slate-400 hover:text-white";
btnS.className = "px-3.5 py-1.5 rounded-xl text-xs font-bold transition-all flex items-center gap-2 text-slate-400 hover:text-white";
btnSel.className = "px-3 py-1.5 rounded-xl text-xs font-bold transition-all flex items-center gap-1.5 text-slate-400 hover:text-white";
if (role === 'teacher') {
document.getElementById('viewTeacherPortal').classList.remove('hidden');
document.getElementById('viewStudentPortal').classList.add('hidden');
roleText.innerHTML = `<i class="fa-solid fa-terminal text-cyan-400"></i> Portal Guru (Mode Edit)`;
btnT.className = "px-3.5 py-1.5 rounded-xl text-xs font-bold transition-all flex items-center gap-2 bg-cyan-500 text-slate-950 shadow-lg shadow-cyan-500/20";
populateTeacherTable();
validateWeights();
} else if (role === 'student') {
document.getElementById('viewTeacherPortal').classList.add('hidden');
document.getElementById('viewStudentPortal').classList.remove('hidden');
roleText.innerHTML = `<i class="fa-solid fa-laptop-code text-sky-400"></i> Portal Siswa (Mode Lihat)`;
btnS.className = "px-3.5 py-1.5 rounded-xl text-xs font-bold transition-all flex items-center gap-2 bg-sky-400 text-slate-950 shadow-lg shadow-sky-400/20";
populateStudentDropdown();
renderStudentDashboard();
}
};
window.backToSelector = function() {
document.getElementById('viewRoleSelector').classList.remove('hidden');
document.getElementById('viewTeacherPortal').classList.add('hidden');
document.getElementById('viewStudentPortal').classList.add('hidden');
document.getElementById('currentRoleText').innerHTML = `<i class="fa-solid fa-shield-halved text-cyan-400"></i> Pilih Portal`;
const btnT = document.getElementById('navBtnTeacher');
const btnS = document.getElementById('navBtnStudent');
const btnSel = document.getElementById('navBtnSelector');
btnT.className = "px-3.5 py-1.5 rounded-xl text-xs font-bold transition-all flex items-center gap-2 text-slate-400 hover:text-white";
btnS.className = "px-3.5 py-1.5 rounded-xl text-xs font-bold transition-all flex items-center gap-2 text-slate-400 hover:text-white";
btnSel.className = "px-3 py-1.5 rounded-xl text-xs font-bold transition-all flex items-center gap-1.5 bg-slate-800 text-white shadow";
};
window.validateWeights = function() {
const w1 = parseFloat(document.getElementById('weightNH').value) || 0;
const w2 = parseFloat(document.getElementById('weightNB').value) || 0;
const w3 = parseFloat(document.getElementById('weightNC').value) || 0;
const w4 = parseFloat(document.getElementById('weightNP').value) || 0;
const total = w1 + w2 + w3 + w4;
const disp = document.getElementById('weightTotalDisplay');
disp.innerText = total + '%';
if (total === 100) {
disp.className = "text-cyan-400 font-bold";
} else {
disp.className = "text-rose-400 font-bold";
}
};
// Formula Calculation Helper
window.calculateFinalScore = function(s, w) {
const nh = parseFloat(s.nh) || 0;
const nb = parseFloat(s.nb) || 0;
const nc = parseFloat(s.nc) || 0;
const np = parseFloat(s.np) || 0;
const totalWeight = w.nh + w.nb + w.nc + w.np;
if (totalWeight === 0) return 0;
const final = (nh * (w.nh / 100)) + (nb * (w.nb / 100)) + (nc * (w.nc / 100)) + (np * (w.np / 100));
return Math.round(final * 100) / 100;
};
window.populateTeacherTable = function() {
const tbody = document.getElementById('teacherTableBody');
tbody.innerHTML = '';
const w = window.appState.weights;
document.getElementById('weightNH').value = w.nh;
document.getElementById('weightNB').value = w.nb;
document.getElementById('weightNC').value = w.nc;
document.getElementById('weightNP').value = w.np;
validateWeights();
window.appState.students.forEach((s, index) => {
const final = calculateFinalScore(s, w);
const isRemedial = final < 75;
const finalScoreClass = isRemedial ? 'text-rose-400 font-extrabold bg-rose-950/40 px-2 py-0.5 rounded-lg border border-rose-500/30 inline-block' : 'text-cyan-400 font-extrabold';
const tr = document.createElement('tr');
tr.className = "hover:bg-slate-800/50 transition-colors";
tr.innerHTML = `
<td class="py-3 px-3">
<div class="font-bold text-white">${s.name}</div>
<div class="text-xs text-slate-400 flex items-center gap-1.5 mt-0.5 font-medium">
<i class="fa-solid fa-phone text-[10px] text-cyan-400"></i> ${s.phone || '-'}
</div>
</td>
<td class="py-3 px-3">
<div class="flex items-center gap-1.5 mb-1">
<span class="px-2 py-0.5 bg-slate-900 text-slate-300 border border-slate-700 text-[10px] font-bold rounded">${s.level || 'SMK'}</span>
<span class="text-xs font-semibold text-slate-300">${s.gradeClass || 'Kelas 10'}</span>
</div>
<span class="px-2.5 py-1 bg-cyan-950/60 border border-cyan-500/30 text-cyan-300 text-xs font-bold rounded-lg inline-block">
${s.major || 'PPLG'}
</span>
</td>
<td class="py-3 px-2 text-center font-medium">${s.nh}</td>
<td class="py-3 px-2 text-center font-medium">${s.nb}</td>
<td class="py-3 px-2 text-center font-medium">${s.nc}</td>
<td class="py-3 px-2 text-center font-medium">${s.np}</td>
<td class="py-3 px-2 text-center"><span class="${finalScoreClass}">${final}</span></td>
<td class="py-3 px-3 text-right space-x-1.5">
<button onclick="editStudent('${s.id}')" class="p-2 bg-slate-800 hover:bg-cyan-500/20 text-slate-300 hover:text-cyan-400 rounded-xl transition-colors border border-slate-700" title="Edit Siswa">
<i class="fa-solid fa-pen-to-square"></i>
</button>
<button onclick="deleteStudent('${s.id}')" class="p-2 bg-slate-800 hover:bg-rose-500/20 text-slate-300 hover:text-rose-400 rounded-xl transition-colors border border-slate-700" title="Hapus Siswa">
<i class="fa-solid fa-trash"></i>
</button>
</td>
`;
tbody.appendChild(tr);
});
};
window.populateStudentDropdown = function() {
const dropdown = document.getElementById('studentSelectorDropdown');
dropdown.innerHTML = '';
window.appState.students.forEach(s => {
const opt = document.createElement('option');
opt.value = s.id;
opt.innerText = `${s.name} (${s.level || 'SMK'} - ${s.gradeClass || ''} - ${s.major || 'IT'})`;
dropdown.appendChild(opt);
});
};
window.renderStudentDashboard = function() {
const dropdown = document.getElementById('studentSelectorDropdown');
if (!dropdown || dropdown.options.length === 0) return;
const selectedId = dropdown.value;
const s = window.appState.students.find(item => item.id === selectedId) || window.appState.students[0];
if (!s) return;
const w = window.appState.weights;
const final = calculateFinalScore(s, w);
document.getElementById('srStudentName').innerText = s.name;
document.getElementById('srStudentLevelClassMajor').innerHTML = `<i class="fa-solid fa-school"></i> ${s.level || 'SMK'} • ${s.gradeClass || 'Kelas 10'} • <i class="fa-solid fa-code"></i> ${s.major || '-'} • <i class="fa-solid fa-phone"></i> ${s.phone || '-'}`;
document.getElementById('srFinalScore').innerText = final.toFixed(1);
document.getElementById('srValNH').innerText = s.nh;
document.getElementById('srContribNH').innerText = ((s.nh * (w.nh / 100))).toFixed(1);
document.getElementById('srValNB').innerText = s.nb;
document.getElementById('srContribNB').innerText = ((s.nb * (w.nb / 100))).toFixed(1);
document.getElementById('srValNC').innerText = s.nc;
document.getElementById('srContribNC').innerText = ((s.nc * (w.nc / 100))).toFixed(1);
document.getElementById('srValNP').innerText = s.np;
document.getElementById('srContribNP').innerText = ((s.np * (w.np / 100))).toFixed(1);
document.getElementById('srTeacherNote').innerText = `"${s.note || 'Belum ada catatan khusus dari pembimbing lab.'}"`;
// Predicate & Status
let predicate = 'A';
if (final < 70) predicate = 'C';
else if (final < 80) predicate = 'B';
else if (final < 90) predicate = 'A-';
else predicate = 'A+';
document.getElementById('srGradePredicate').innerText = predicate;
const passBadge = document.getElementById('srStatusPass');
if (final >= 75) {
passBadge.innerText = 'Tuntas / Kompeten';
passBadge.className = 'px-2.5 py-1 bg-emerald-500 rounded-lg text-[10px] font-bold text-white shadow';
} else {
passBadge.innerText = 'Remidi / Perbaikan';
passBadge.className = 'px-3 py-1 bg-rose-600 animate-pulse rounded-lg text-[11px] font-extrabold text-white shadow-lg border border-rose-400';
}
};
window.refreshUIViews = function() {
if (!document.getElementById('viewTeacherPortal').classList.contains('hidden')) {
populateTeacherTable();
}
if (!document.getElementById('viewStudentPortal').classList.contains('hidden')) {
populateStudentDropdown();
renderStudentDashboard();
}
};
window.updateModalMajorOptions = function(selectedMajor = 'PPLG', selectedGradeClass = 'Kelas 10') {
const level = document.getElementById('inputStudentLevel').value;
const majorSelect = document.getElementById('inputStudentMajor');
const gradeClassSelect = document.getElementById('inputStudentGradeClass');
// Grade classes based on level
gradeClassSelect.innerHTML = '';
if (level === 'SMP') {
['Kelas 7', 'Kelas 8', 'Kelas 9'].forEach(cls => {
const opt = document.createElement('option');
opt.value = cls;
opt.innerText = cls;
if (cls === selectedGradeClass) opt.selected = true;
gradeClassSelect.appendChild(opt);
});
} else {
['Kelas 10', 'Kelas 11', 'Kelas 12'].forEach(cls => {
const opt = document.createElement('option');
opt.value = cls;
opt.innerText = cls;
if (cls === selectedGradeClass) opt.selected = true;
gradeClassSelect.appendChild(opt);
});
}
// Majors based on level
majorSelect.innerHTML = '';
if (level === 'SMP') {
const opt = document.createElement('option');
opt.value = 'Informatika Dasar';
opt.innerText = 'Informatika Dasar (SMP)';
majorSelect.appendChild(opt);
} else {
const majors = [
{ code: 'PPLG', name: 'PPLG (Pengembangan Perangkat Lunak & GIM)' },
{ code: 'TJKT', name: 'TJKT (Teknik Jaringan Komputer & Telekomunikasi)' },
{ code: 'MPLB', name: 'MPLB (Manajemen Perkantoran & Layanan Bisnis)' },
{ code: 'AKL', name: 'AKL (Akuntansi & Keuangan Lembaga)' },
{ code: 'TKR', name: 'TKR (Teknik Kendaraan Ringan)' },
{ code: 'TAV', name: 'TAV (Teknik Audio Video)' },
{ code: 'DKV', name: 'DKV (Desain Komunikasi Visual)' },
{ code: 'ANIMASI', name: 'Animasi & Multimedia' }
];
majors.forEach(m => {
const opt = document.createElement('option');
opt.value = m.code;
opt.innerText = m.name;
if (m.code === selectedMajor) opt.selected = true;
majorSelect.appendChild(opt);
});
}
};
// Modal Form Handlers for Teacher adding/editing students
window.openAddStudentModal = function() {
document.getElementById('studentModalTitle').innerHTML = '<i class="fa-solid fa-user-plus text-cyan-400"></i> Tambah Siswa Baru';
document.getElementById('editStudentId').value = '';
document.getElementById('inputStudentName').value = '';
document.getElementById('inputStudentLevel').value = 'SMK';
updateModalMajorOptions('PPLG', 'Kelas 10');
document.getElementById('inputStudentPhone').value = '';
document.getElementById('inputNH').value = '80';
document.getElementById('inputNB').value = '80';
document.getElementById('inputNC').value = '85';
document.getElementById('inputNP').value = '85';
document.getElementById('inputNote').value = 'Menunjukkan perkembangan logika pemrograman yang positif.';
document.getElementById('studentModal').classList.remove('hidden');
};
window.editStudent = function(id) {
const s = window.appState.students.find(item => item.id === id);
if (!s) return;
document.getElementById('studentModalTitle').innerHTML = '<i class="fa-solid fa-user-pen text-cyan-400"></i> Edit Nilai Siswa';
document.getElementById('editStudentId').value = s.id;
document.getElementById('inputStudentName').value = s.name;
document.getElementById('inputStudentLevel').value = s.level || 'SMK';
updateModalMajorOptions(s.major || 'PPLG', s.gradeClass || 'Kelas 10');
document.getElementById('inputStudentPhone').value = s.phone || '';
document.getElementById('inputNH').value = s.nh;
document.getElementById('inputNB').value = s.nb;
document.getElementById('inputNC').value = s.nc;
document.getElementById('inputNP').value = s.np;
document.getElementById('inputNote').value = s.note || '';
document.getElementById('studentModal').classList.remove('hidden');
};
window.closeStudentModal = function() {
document.getElementById('studentModal').classList.add('hidden');
};
window.saveStudentData = function() {
const id = document.getElementById('editStudentId').value;
const name = document.getElementById('inputStudentName').value.trim();
const level = document.getElementById('inputStudentLevel').value;
const gradeClass = document.getElementById('inputStudentGradeClass').value;
const major = document.getElementById('inputStudentMajor').value;
const phone = document.getElementById('inputStudentPhone').value.trim();
const nh = parseFloat(document.getElementById('inputNH').value) || 0;
const nb = parseFloat(document.getElementById('inputNB').value) || 0;
const nc = parseFloat(document.getElementById('inputNC').value) || 0;
const np = parseFloat(document.getElementById('inputNP').value) || 0;
const note = document.getElementById('inputNote').value.trim();
if (!name) {
showModal("Validasi Gagal", "Nama siswa wajib diisi.", "warning");
return;
}
if (id) {
const s = window.appState.students.find(item => item.id === id);
if (s) {
s.name = name;
s.level = level;
s.gradeClass = gradeClass;
s.major = major;
s.phone = phone;
s.nh = nh; s.nb = nb; s.nc = nc; s.np = np; s.note = note;
}
} else {
const newStudent = {
id: 's_' + Date.now(),
name, level, gradeClass, major, phone, nh, nb, nc, np, note
};
window.appState.students.push(newStudent);
}
closeStudentModal();
populateTeacherTable();
showModal("Berhasil", "Data siswa berhasil diperbarui.", "success");
};
window.deleteStudent = function(id) {
window.appState.students = window.appState.students.filter(item => item.id !== id);
populateTeacherTable();
showModal("Berhasil", "Siswa telah dihapus dari daftar.", "success");
};
// Custom Modal helper
window.showModal = function(title, message, type = 'info') {
document.getElementById('modalTitle').innerText = title;
document.getElementById('modalMessage').innerText = message;
const iconDiv = document.getElementById('modalIcon');
if (type === 'success') {
iconDiv.className = 'w-14 h-14 rounded-2xl bg-emerald-500/10 text-emerald-400 flex items-center justify-center text-2xl mb-5 border border-emerald-500/20 shadow-lg shadow-emerald-500/10';
iconDiv.innerHTML = '<i class="fa-solid fa-circle-check"></i>';
} else if (type === 'warning') {
iconDiv.className = 'w-14 h-14 rounded-2xl bg-amber-500/10 text-amber-400 flex items-center justify-center text-2xl mb-5 border border-amber-500/20 shadow-lg shadow-amber-500/10';
iconDiv.innerHTML = '<i class="fa-solid fa-triangle-exclamation"></i>';
} else {
iconDiv.className = 'w-14 h-14 rounded-2xl bg-cyan-500/10 text-cyan-400 flex items-center justify-center text-2xl mb-5 border border-cyan-500/20 shadow-lg shadow-cyan-500/10';
iconDiv.innerHTML = '<i class="fa-solid fa-circle-info"></i>';
}
document.getElementById('customModal').classList.remove('hidden');
};
window.closeModal = function(val) {
document.getElementById('customModal').classList.add('hidden');
};
</script>
</body>
</html>
suryogarage
0 Comments