Spaces:
Running
Running
| <html lang="id"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Aplikasi Catatan Pembelajaran</title> | |
| <!-- jsPDF + autoTable --> | |
| <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.8.2/jspdf.plugin.autotable.min.js"></script> | |
| <!-- SheetJS --> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> | |
| <style> | |
| body { font-family: 'Segoe UI', Arial, sans-serif; margin: 0; background: #f0f2f5; } | |
| h1 { text-align: center; color: #1a1a1a; margin: 20px 0; } | |
| .container { max-width: 1200px; margin: 0 auto; padding: 20px; } | |
| .card { background: white; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); overflow: hidden; } | |
| .tabs { display: flex; background: #f8f9fa; border-bottom: 1px solid #dee2e6; } | |
| .tablink { flex: 1; padding: 16px; text-align: center; font-size: 18px; font-weight: 600; cursor: pointer; border: none; background: transparent; } | |
| .tablink:hover { background: #e9ecef; } | |
| .tablink.active { background: #007bff; color: white; } | |
| .tabcontent { padding: 30px; display: none; } | |
| .tabcontent.active { display: block; } | |
| label { display: block; margin: 15px 0 8px; font-weight: 600; color: #333; } | |
| input, select, textarea { width: 100%; padding: 12px; border: 1px solid #ced4da; border-radius: 8px; box-sizing: border-box; font-size: 16px; } | |
| textarea { resize: vertical; min-height: 120px; } | |
| button { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 15px; margin: 5px; } | |
| button:hover { background: #0056b3; } | |
| button.secondary { background: #6c757d; } | |
| button.secondary:hover { background: #545b62; } | |
| button.danger { background: #dc3545; } | |
| button.danger:hover { background: #c82333; } | |
| button.small { padding: 6px 12px; font-size: 13px; } | |
| .form-group { margin-bottom: 10px; } | |
| .filter-group { display: flex; gap: 15px; flex-wrap: wrap; align-items: end; margin-bottom: 20px; } | |
| .filter-group > div { flex: 1; min-width: 180px; } | |
| table { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 15px; } | |
| th { background: #007bff; color: white; padding: 14px; text-align: left; } | |
| td { padding: 12px; border-bottom: 1px solid #dee2e6; vertical-align: top; } | |
| tr:hover { background: #f8f9fa; } | |
| .text-wrap { white-space: pre-wrap; word-wrap: break-word; } | |
| .actions { margin: 20px 0; display: flex; flex-wrap: wrap; gap: 10px; align-items: center; } | |
| .action-buttons { white-space: nowrap; } | |
| #editStatus { background: #e7f3ff; padding: 12px; border-radius: 8px; border-left: 4px solid #007bff; } | |
| @media (max-width: 768px) { | |
| .filter-group { flex-direction: column; } | |
| .action-buttons button { display: block; width: 100%; margin: 5px 0; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>Aplikasi Catatan Pembelajaran</h1> | |
| <div class="card"> | |
| <div class="tabs"> | |
| <button class="tablink active" onclick="openTab(event, 'Input')">Input Pembelajaran</button> | |
| <button class="tablink" onclick="openTab(event, 'Daftar')">Daftar Pembelajaran</button> | |
| </div> | |
| <!-- Tab Input --> | |
| <div id="Input" class="tabcontent active"> | |
| <h2 style="margin-top:0; color:#333;">Input / Edit Data Pembelajaran</h2> | |
| <label>Jenis Pembelajaran</label> | |
| <select id="jenisSelect" onchange="updateIndicatorDropdown()"></select> | |
| <div id="manualJenisInput" class="form-group" style="display:none;"> | |
| <label>Tambah Jenis Baru</label> | |
| <input type="text" id="jenisManual" placeholder="Contoh: Pemrograman, Fisika, Sejarah"> | |
| </div> | |
| <label>Indicator (Sub Bab yang Dibahas)</label> | |
| <select id="indicatorSelect"></select> | |
| <div id="manualIndicatorInput" class="form-group" style="display:none;"> | |
| <label>Tambah Sub Bab Baru (untuk jenis ini)</label> | |
| <input type="text" id="indicatorManual" placeholder="Contoh: Aljabar, HTML, Perang Dunia II"> | |
| </div> | |
| <label>Hasil Pembelajaran</label> | |
| <textarea id="hasil" placeholder="Tuliskan detail apa yang telah Anda pelajari..."></textarea> | |
| <label>Kesimpulan</label> | |
| <textarea id="kesimpulan" placeholder="Tuliskan poin penting atau kesimpulan..."></textarea> | |
| <div> | |
| <button onclick="simpanData()">Simpan Pembelajaran</button> | |
| <button class="secondary" onclick="resetForm()">Batal / Baru</button> | |
| </div> | |
| <div id="editStatus" style="display:none;"> | |
| <strong>📝 Sedang mengedit entri nomor: <span id="editIndex"></span></span> | |
| </div> | |
| </div> | |
| <!-- Tab Daftar --> | |
| <div id="Daftar" class="tabcontent"> | |
| <h2 style="margin-top:0; color:#333;">Daftar Semua Pembelajaran</h2> | |
| <div class="filter-group"> | |
| <div> | |
| <label>Filter Jenis</label> | |
| <select id="filterJenis" onchange="updateFilterIndicator(); terapkanFilter()"> | |
| <option value="">Semua Jenis</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label>Filter Indicator (Sub Bab)</label> | |
| <select id="filterIndicator" onchange="terapkanFilter()"> | |
| <option value="">Semua Sub Bab</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label>Filter Kata Kunci</label> | |
| <input type="text" id="filterKata" list="suggestions" placeholder="Ketik untuk mencari..." oninput="terapkanFilter()"> | |
| <datalist id="suggestions"></datalist> | |
| </div> | |
| <div style="align-self: end;"> | |
| <button onclick="terapkanFilter()">Terapkan Filter</button> | |
| <button class="secondary" onclick="resetFilter()">Reset Filter</button> | |
| </div> | |
| </div> | |
| <div class="actions"> | |
| <div> | |
| <label style="display:inline-block; margin-right:10px;">Import dari Excel:</label> | |
| <input type="file" id="importFile" accept=".xlsx, .xls" style="display:inline-block; width:auto;"> | |
| <button onclick="importExcel()">Import Data</button> | |
| </div> | |
| <div> | |
| <button onclick="exportExcel()">Export ke Excel</button> | |
| <button onclick="exportPDF()">Export ke PDF</button> | |
| </div> | |
| </div> | |
| <table id="tabelPembelajaran"> | |
| <thead> | |
| <tr> | |
| <th>No</th> | |
| <th>Tanggal</th> | |
| <th>Jenis</th> | |
| <th>Indicator</th> | |
| <th>Hasil Pembelajaran</th> | |
| <th>Kesimpulan</th> | |
| <th>Aksi</th> | |
| </tr> | |
| </thead> | |
| <tbody></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const { jsPDF } = window.jspdf; | |
| let dataPembelajaran = JSON.parse(localStorage.getItem('pembelajaran')) || []; | |
| let subBabByJenis = {}; | |
| let editIndex = -1; | |
| function buildSubBabMapping() { | |
| subBabByJenis = {}; | |
| dataPembelajaran.forEach(entry => { | |
| if (!subBabByJenis[entry.jenis]) { | |
| subBabByJenis[entry.jenis] = new Set(); | |
| } | |
| subBabByJenis[entry.jenis].add(entry.indikator); | |
| }); | |
| Object.keys(subBabByJenis).forEach(jenis => { | |
| subBabByJenis[jenis] = Array.from(subBabByJenis[jenis]).sort(); | |
| }); | |
| } | |
| function getJenisList() { | |
| return [...new Set(dataPembelajaran.map(d => d.jenis))].sort(); | |
| } | |
| function updateJenisDropdown() { | |
| const jenisSelect = document.getElementById('jenisSelect'); | |
| const filterJenis = document.getElementById('filterJenis'); | |
| const jenisList = getJenisList(); | |
| jenisSelect.innerHTML = ''; | |
| filterJenis.innerHTML = '<option value="">Semua Jenis</option>'; | |
| const defaultJenis = jenisList.length === 0 ? ["Matematika", "Bahasa", "Sains"] : jenisList; | |
| defaultJenis.forEach(j => { | |
| jenisSelect.add(new Option(j, j)); | |
| filterJenis.add(new Option(j, j)); | |
| }); | |
| jenisSelect.add(new Option('--- Tambah Jenis Baru ---', 'new')); | |
| } | |
| function updateIndicatorDropdown(selectedJenis = null) { | |
| const jenis = selectedJenis || document.getElementById('jenisSelect').value; | |
| const indicatorSelect = document.getElementById('indicatorSelect'); | |
| const manualDiv = document.getElementById('manualIndicatorInput'); | |
| indicatorSelect.innerHTML = ''; | |
| manualDiv.style.display = 'none'; | |
| if (jenis === 'new' || !jenis) { | |
| indicatorSelect.innerHTML = '<option value="">-- Pilih jenis dulu --</option>'; | |
| return; | |
| } | |
| const subBabList = subBabByJenis[jenis] || []; | |
| if (subBabList.length === 0) { | |
| indicatorSelect.add(new Option('-- Belum ada sub bab --', '')); | |
| } else { | |
| subBabList.forEach(sb => indicatorSelect.add(new Option(sb, sb))); | |
| } | |
| indicatorSelect.add(new Option('--- Tambah Sub Bab Baru ---', 'new')); | |
| } | |
| function updateFilterIndicator() { | |
| const selectedJenis = document.getElementById('filterJenis').value; | |
| const filterIndicator = document.getElementById('filterIndicator'); | |
| filterIndicator.innerHTML = '<option value="">Semua Sub Bab</option>'; | |
| if (!selectedJenis) { | |
| const allIndicators = [...new Set(dataPembelajaran.map(d => d.indikator))].sort(); | |
| allIndicators.forEach(ind => filterIndicator.add(new Option(ind, ind))); | |
| } else { | |
| const subBabList = subBabByJenis[selectedJenis] || []; | |
| subBabList.forEach(ind => filterIndicator.add(new Option(ind, ind))); | |
| } | |
| } | |
| function updateAutocompleteSuggestions() { | |
| const datalist = document.getElementById('suggestions'); | |
| datalist.innerHTML = ''; | |
| const allWords = new Set(); | |
| dataPembelajaran.forEach(entry => { | |
| [...entry.hasil.split(/\s+/), ...entry.kesimpulan.split(/\s+/)].forEach(word => { | |
| word = word.replace(/[.,!?()"]/g, '').toLowerCase(); | |
| if (word.length >= 3) allWords.add(word); | |
| }); | |
| }); | |
| Array.from(allWords).sort().slice(0, 50).forEach(word => { | |
| const option = document.createElement('option'); | |
| option.value = word; | |
| datalist.appendChild(option); | |
| }); | |
| } | |
| // Event listeners | |
| document.getElementById('jenisSelect').addEventListener('change', function() { | |
| document.getElementById('manualJenisInput').style.display = this.value === 'new' ? 'block' : 'none'; | |
| updateIndicatorDropdown(); | |
| }); | |
| document.getElementById('indicatorSelect').addEventListener('change', function() { | |
| document.getElementById('manualIndicatorInput').style.display = this.value === 'new' ? 'block' : 'none'; | |
| }); | |
| function simpanData() { | |
| let jenis = document.getElementById('jenisSelect').value; | |
| if (jenis === 'new') { | |
| jenis = document.getElementById('jenisManual').value.trim(); | |
| if (!jenis) return alert('Masukkan nama jenis baru!'); | |
| } | |
| let indikator = document.getElementById('indicatorSelect').value; | |
| if (indikator === 'new') { | |
| indikator = document.getElementById('indicatorManual').value.trim(); | |
| if (!indikator) return alert('Masukkan nama sub bab baru!'); | |
| } | |
| const hasil = document.getElementById('hasil').value.trim(); | |
| const kesimpulan = document.getElementById('kesimpulan').value.trim(); | |
| if (!jenis || !indikator || !hasil || !kesimpulan) { | |
| return alert('Semua field wajib diisi!'); | |
| } | |
| const entry = { | |
| tanggal: editIndex === -1 ? new Date().toLocaleString('id-ID') : dataPembelajaran[editIndex].tanggal, | |
| jenis, | |
| indikator, | |
| hasil, | |
| kesimpulan | |
| }; | |
| if (editIndex === -1) { | |
| dataPembelajaran.push(entry); | |
| } else { | |
| dataPembelajaran[editIndex] = entry; | |
| } | |
| localStorage.setItem('pembelajaran', JSON.stringify(dataPembelajaran)); | |
| buildSubBabMapping(); | |
| updateJenisDropdown(); | |
| updateIndicatorDropdown(); | |
| updateFilterIndicator(); | |
| updateAutocompleteSuggestions(); | |
| resetForm(); | |
| alert(editIndex === -1 ? 'Data berhasil disimpan!' : 'Perubahan berhasil disimpan!'); | |
| if (document.getElementById('Daftar').classList.contains('active')) tampilkanData(); | |
| } | |
| function resetForm() { | |
| editIndex = -1; | |
| document.getElementById('editStatus').style.display = 'none'; | |
| updateJenisDropdown(); | |
| document.getElementById('manualJenisInput').style.display = 'none'; | |
| document.getElementById('jenisManual').value = ''; | |
| updateIndicatorDropdown(); | |
| document.getElementById('manualIndicatorInput').style.display = 'none'; | |
| document.getElementById('indicatorManual').value = ''; | |
| document.getElementById('hasil').value = ''; | |
| document.getElementById('kesimpulan').value = ''; | |
| } | |
| // FUNGSI EDIT YANG DIPERBAIKI | |
| function editData(index) { | |
| const data = dataPembelajaran[index]; | |
| editIndex = index; | |
| // Pindah ke tab Input | |
| openTab(null, 'Input'); | |
| // Pastikan jenis ada di dropdown | |
| const jenisSelect = document.getElementById('jenisSelect'); | |
| if (!Array.from(jenisSelect.options).find(opt => opt.value === data.jenis)) { | |
| jenisSelect.add(new Option(data.jenis, data.jenis), 0); | |
| } | |
| jenisSelect.value = data.jenis; | |
| // Update dropdown indicator sesuai jenis | |
| updateIndicatorDropdown(data.jenis); | |
| // Pastikan indicator ada di dropdown | |
| const indicatorSelect = document.getElementById('indicatorSelect'); | |
| if (!Array.from(indicatorSelect.options).find(opt => opt.value === data.indikator)) { | |
| // Tambahkan sebelum opsi "Tambah baru" | |
| const newOption = new Option(data.indikator, data.indikator); | |
| indicatorSelect.insertBefore(newOption, indicatorSelect.lastElementChild); | |
| } | |
| indicatorSelect.value = data.indikator; | |
| // Isi teks | |
| document.getElementById('hasil').value = data.hasil; | |
| document.getElementById('kesimpulan').value = data.kesimpulan; | |
| // Tampilkan status edit | |
| document.getElementById('editStatus').style.display = 'block'; | |
| document.getElementById('editIndex').textContent = index + 1; | |
| // Scroll ke atas form | |
| document.getElementById('Input').scrollIntoView({ behavior: 'smooth' }); | |
| } | |
| function deleteData(index) { | |
| if (confirm('Yakin ingin menghapus entri ini? Tindakan ini tidak dapat dibatalkan.')) { | |
| dataPembelajaran.splice(index, 1); | |
| localStorage.setItem('pembelajaran', JSON.stringify(dataPembelajaran)); | |
| buildSubBabMapping(); | |
| updateJenisDropdown(); | |
| updateIndicatorDropdown(); | |
| updateFilterIndicator(); | |
| updateAutocompleteSuggestions(); | |
| tampilkanData(); | |
| alert('Data berhasil dihapus!'); | |
| } | |
| } | |
| function tampilkanData() { | |
| const filterJenis = document.getElementById('filterJenis').value; | |
| const filterIndicatorVal = document.getElementById('filterIndicator').value; | |
| const filterKata = document.getElementById('filterKata').value.toLowerCase().trim(); | |
| let filtered = dataPembelajaran; | |
| if (filterJenis) filtered = filtered.filter(d => d.jenis === filterJenis); | |
| if (filterIndicatorVal) filtered = filtered.filter(d => d.indikator === filterIndicatorVal); | |
| if (filterKata) { | |
| filtered = filtered.filter(d => | |
| d.hasil.toLowerCase().includes(filterKata) || | |
| d.kesimpulan.toLowerCase().includes(filterKata) | |
| ); | |
| } | |
| const tbody = document.querySelector('#tabelPembelajaran tbody'); | |
| tbody.innerHTML = ''; | |
| filtered.forEach((d, i) => { | |
| const originalIndex = dataPembelajaran.indexOf(d); | |
| const row = tbody.insertRow(); | |
| row.insertCell(0).textContent = i + 1; | |
| row.insertCell(1).textContent = d.tanggal; | |
| row.insertCell(2).textContent = d.jenis; | |
| row.insertCell(3).textContent = d.indikator; | |
| row.insertCell(4).innerHTML = '<div class="text-wrap">' + d.hasil.replace(/\n/g, '<br>') + '</div>'; | |
| row.insertCell(5).innerHTML = '<div class="text-wrap">' + d.kesimpulan.replace(/\n/g, '<br>') + '</div>'; | |
| const cellAksi = row.insertCell(6); | |
| cellAksi.className = 'action-buttons'; | |
| cellAksi.innerHTML = ` | |
| <button class="small" onclick="editData(${originalIndex})" style="background:#28a745;">Edit</button> | |
| <button class="small danger" onclick="deleteData(${originalIndex})">Hapus</button> | |
| `; | |
| }); | |
| } | |
| function terapkanFilter() { | |
| tampilkanData(); | |
| } | |
| function resetFilter() { | |
| document.getElementById('filterJenis').value = ''; | |
| updateFilterIndicator(); | |
| document.getElementById('filterIndicator').value = ''; | |
| document.getElementById('filterKata').value = ''; | |
| tampilkanData(); | |
| } | |
| // Import, Export tetap sama | |
| function importExcel() { /* ... kode import sama seperti sebelumnya ... */ } | |
| function exportExcel() { /* ... kode export sama ... */ } | |
| function exportPDF() { /* ... kode export sama ... */ } | |
| // Tambahkan kode import/export lengkap di sini jika diperlukan (sama seperti versi sebelumnya) | |
| function openTab(evt, tabName) { | |
| document.querySelectorAll(".tabcontent").forEach(el => el.classList.remove("active")); | |
| document.querySelectorAll(".tablink").forEach(el => el.classList.remove("active")); | |
| document.getElementById(tabName).classList.add("active"); | |
| if (evt) evt.currentTarget.classList.add("active"); | |
| if (tabName === 'Daftar') { | |
| updateJenisDropdown(); | |
| updateFilterIndicator(); | |
| updateAutocompleteSuggestions(); | |
| tampilkanData(); | |
| } | |
| } | |
| // Inisialisasi | |
| buildSubBabMapping(); | |
| updateJenisDropdown(); | |
| updateIndicatorDropdown(); | |
| updateFilterIndicator(); | |
| updateAutocompleteSuggestions(); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-qwensite.hf.space/logo.svg" alt="qwensite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-qwensite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >QwenSite</a> - 🧬 <a href="https://enzostvs-qwensite.hf.space?remix=alterzick/notepad-v1" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |