// ============================================ // APLIKASI DOKUMENTASI NASABAH LPD GERANA // ============================================ // Global Variables let stream = null; let capturedImage = null; let db = null; let currentDetailId = null; // DOM Elements const elements = { // Webcam webcam: document.getElementById('webcam'), canvas: document.getElementById('canvas'), preview: document.getElementById('preview'), previewImage: document.getElementById('previewImage'), // Buttons startBtn: document.getElementById('startBtn'), captureBtn: document.getElementById('captureBtn'), retakeBtn: document.getElementById('retakeBtn'), saveBtn: document.getElementById('saveBtn'), printBtn: document.getElementById('printBtn'), deleteBtn: document.getElementById('deleteBtn'), exportBtn: document.getElementById('exportBtn'), // Form form: document.getElementById('nasabahForm'), nama: document.getElementById('nama'), noAnggota: document.getElementById('noAnggota'), jenisPerjanjian: document.getElementById('jenisPerjanjian'), tanggal: document.getElementById('tanggal'), catatan: document.getElementById('catatan'), // Gallery gallery: document.getElementById('gallery'), emptyState: document.getElementById('emptyState'), searchInput: document.getElementById('searchInput'), galleryStats: document.getElementById('galleryStats'), // Modal modal: document.getElementById('detailModal'), modalBody: document.getElementById('modalBody'), closeModal: document.querySelector('.close-modal'), // Status webcamStatus: document.getElementById('webcamStatus'), toast: document.getElementById('toast') }; // ============================================ // API CONFIGURATION // ============================================ const API_BASE_URL = 'http://localhost:3000/api'; async function checkApiConnection() { try { const response = await fetch(`${API_BASE_URL}/health`); if (!response.ok) { throw new Error('API not responding'); } console.log('✅ API connection successful'); return true; } catch (error) { console.error('❌ API connection failed:', error); showToast('⚠️ Tidak dapat terhubung ke server. Pastikan server berjalan.', 'error'); return false; } } // ============================================ // DATABASE OPERATIONS // ============================================ async function saveToDatabase(data) { try { const response = await fetch(`${API_BASE_URL}/dokumentasi`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to save'); } const result = await response.json(); console.log('Data berhasil disimpan dengan ID:', result.id); return result.id; } catch (error) { console.error('Error saving to database:', error); throw error; } } async function getAllData() { try { const response = await fetch(`${API_BASE_URL}/dokumentasi`); if (!response.ok) { throw new Error('Failed to fetch data'); } const result = await response.json(); return result.data || []; } catch (error) { console.error('Error fetching data:', error); throw error; } } async function getDataById(id) { try { const response = await fetch(`${API_BASE_URL}/dokumentasi/${id}`); if (!response.ok) { throw new Error('Data not found'); } const result = await response.json(); return result.data; } catch (error) { console.error('Error fetching data by ID:', error); throw error; } } async function deleteData(id) { try { const response = await fetch(`${API_BASE_URL}/dokumentasi/${id}`, { method: 'DELETE' }); if (!response.ok) { throw new Error('Failed to delete'); } return true; } catch (error) { console.error('Error deleting data:', error); throw error; } } // ============================================ // WEBCAM FUNCTIONS // ============================================ async function startWebcam() { try { showStatus('Meminta akses webcam...', 'info'); stream = await navigator.mediaDevices.getUserMedia({ video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: 'user' }, audio: false }); elements.webcam.srcObject = stream; elements.startBtn.style.display = 'none'; elements.captureBtn.style.display = 'inline-flex'; showStatus('Webcam aktif. Posisikan nasabah dan klik "Ambil Foto"', 'success'); showToast('✅ Webcam berhasil diaktifkan', 'success'); } catch (error) { console.error('Error accessing webcam:', error); showStatus('Gagal mengakses webcam. Pastikan webcam terhubung dan izin diberikan.', 'error'); showToast('❌ Gagal mengakses webcam', 'error'); } } function capturePhoto() { const canvas = elements.canvas; const context = canvas.getContext('2d'); // Set canvas size to match video canvas.width = elements.webcam.videoWidth; canvas.height = elements.webcam.videoHeight; // Draw video frame to canvas context.drawImage(elements.webcam, 0, 0, canvas.width, canvas.height); // Get image data capturedImage = canvas.toDataURL('image/jpeg', 0.9); // Show preview elements.previewImage.src = capturedImage; elements.webcam.style.display = 'none'; elements.preview.style.display = 'block'; // Update buttons elements.captureBtn.style.display = 'none'; elements.retakeBtn.style.display = 'inline-flex'; elements.saveBtn.style.display = 'inline-flex'; showStatus('Foto berhasil diambil. Periksa hasilnya dan klik "Simpan" jika sudah sesuai.', 'success'); showToast('📸 Foto berhasil diambil', 'success'); } function retakePhoto() { capturedImage = null; elements.webcam.style.display = 'block'; elements.preview.style.display = 'none'; elements.retakeBtn.style.display = 'none'; elements.saveBtn.style.display = 'none'; elements.captureBtn.style.display = 'inline-flex'; showStatus('Webcam aktif. Ambil foto ulang.', 'info'); } function stopWebcam() { if (stream) { stream.getTracks().forEach(track => track.stop()); stream = null; } elements.webcam.srcObject = null; elements.webcam.style.display = 'block'; elements.preview.style.display = 'none'; elements.startBtn.style.display = 'inline-flex'; elements.captureBtn.style.display = 'none'; elements.retakeBtn.style.display = 'none'; elements.saveBtn.style.display = 'none'; capturedImage = null; } // ============================================ // FORM FUNCTIONS // ============================================ async function saveDocumentation() { // Validate form if (!elements.form.checkValidity()) { elements.form.reportValidity(); showToast('⚠️ Lengkapi semua data yang wajib diisi', 'error'); return; } // Validate photo if (!capturedImage) { showToast('⚠️ Ambil foto terlebih dahulu', 'error'); return; } // Prepare data const data = { nama: elements.nama.value.trim(), noAnggota: elements.noAnggota.value.trim(), jenisPerjanjian: elements.jenisPerjanjian.value, tanggal: elements.tanggal.value, catatan: elements.catatan.value.trim(), foto: capturedImage, timestamp: new Date().toISOString() }; try { await saveToDatabase(data); showToast('✅ Dokumentasi berhasil disimpan', 'success'); // Reset form and webcam elements.form.reset(); setDefaultDate(); stopWebcam(); showStatus('', ''); // Reload gallery await loadGallery(); } catch (error) { console.error('Error saving documentation:', error); showToast('❌ Gagal menyimpan dokumentasi', 'error'); } } function setDefaultDate() { const today = new Date().toISOString().split('T')[0]; elements.tanggal.value = today; } // ============================================ // GALLERY FUNCTIONS // ============================================ async function loadGallery(searchTerm = '') { try { const allData = await getAllData(); // Filter by search term let filteredData = allData; if (searchTerm) { const term = searchTerm.toLowerCase(); filteredData = allData.filter(item => item.nama.toLowerCase().includes(term) || item.noAnggota.toLowerCase().includes(term) ); } // Sort by timestamp (newest first) filteredData.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)); // Update stats updateGalleryStats(filteredData.length, allData.length); // Show empty state if no data if (filteredData.length === 0) { elements.gallery.innerHTML = ''; elements.emptyState.style.display = 'block'; return; } elements.emptyState.style.display = 'none'; // Render gallery items elements.gallery.innerHTML = filteredData.map(item => ` `).join(''); } catch (error) { console.error('Error loading gallery:', error); showToast('❌ Gagal memuat galeri', 'error'); } } function updateGalleryStats(shown, total) { if (total === 0) { elements.galleryStats.innerHTML = ''; return; } let statsText = `📊 Total: ${total} dokumentasi`; if (shown < total) { statsText += ` • Ditampilkan: ${shown}`; } elements.galleryStats.innerHTML = statsText; } // ============================================ // DETAIL & PRINT FUNCTIONS // ============================================ async function showDetail(id) { try { const data = await getDataById(id); if (!data) { showToast('❌ Data tidak ditemukan', 'error'); return; } currentDetailId = id; elements.modalBody.innerHTML = `
${escapeHtml(data.nama)}

📋 Detail Dokumentasi

Nama Lengkap
${escapeHtml(data.nama)}
Nomor Anggota
${escapeHtml(data.noAnggota)}
Jenis Perjanjian
${escapeHtml(data.jenisPerjanjian)}
Tanggal Penandatanganan
${formatDate(data.tanggal)}
${data.catatan ? `
Catatan
${escapeHtml(data.catatan)}
` : ''}
Waktu Pengambilan Foto
${formatDateTime(data.timestamp)}
`; elements.modal.classList.add('show'); } catch (error) { console.error('Error showing detail:', error); showToast('❌ Gagal memuat detail', 'error'); } } function closeModal() { elements.modal.classList.remove('show'); currentDetailId = null; } function printDetail() { window.print(); } async function deleteDetail() { if (!currentDetailId) return; if (!confirm('Apakah Anda yakin ingin menghapus dokumentasi ini?')) { return; } try { await deleteData(currentDetailId); showToast('✅ Dokumentasi berhasil dihapus', 'success'); closeModal(); await loadGallery(); } catch (error) { console.error('Error deleting data:', error); showToast('❌ Gagal menghapus dokumentasi', 'error'); } } // ============================================ // EXPORT FUNCTION // ============================================ async function exportData() { try { const allData = await getAllData(); if (allData.length === 0) { showToast('⚠️ Tidak ada data untuk diekspor', 'error'); return; } // Create CSV data (without photos for file size) const csvData = convertToCSV(allData); // Download CSV const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.setAttribute('href', url); link.setAttribute('download', `dokumentasi_nasabah_${new Date().toISOString().split('T')[0]}.csv`); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); showToast('✅ Data berhasil diekspor', 'success'); } catch (error) { console.error('Error exporting data:', error); showToast('❌ Gagal mengekspor data', 'error'); } } function convertToCSV(data) { const headers = ['ID', 'Nama', 'No. Anggota', 'Jenis Perjanjian', 'Tanggal', 'Catatan', 'Waktu Input']; const rows = data.map(item => [ item.id, item.nama, item.noAnggota, item.jenisPerjanjian, formatDate(item.tanggal), item.catatan || '', formatDateTime(item.timestamp) ]); const csvContent = [ headers.join(','), ...rows.map(row => row.map(cell => `"${cell}"`).join(',')) ].join('\n'); return csvContent; } // ============================================ // UTILITY FUNCTIONS // ============================================ function showStatus(message, type) { if (!message) { elements.webcamStatus.innerHTML = ''; elements.webcamStatus.className = 'status-message'; return; } elements.webcamStatus.innerHTML = message; elements.webcamStatus.className = `status-message ${type}`; } function showToast(message, type = 'success') { elements.toast.textContent = message; elements.toast.className = `toast ${type} show`; setTimeout(() => { elements.toast.classList.remove('show'); }, 3000); } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function formatDate(dateString) { const options = { year: 'numeric', month: 'long', day: 'numeric' }; return new Date(dateString).toLocaleDateString('id-ID', options); } function formatTime(isoString) { return new Date(isoString).toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' }); } function formatDateTime(isoString) { const date = new Date(isoString); return `${date.toLocaleDateString('id-ID', { year: 'numeric', month: 'long', day: 'numeric' })} pukul ${date.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' })}`; } // ============================================ // EVENT LISTENERS // ============================================ function setupEventListeners() { // Webcam controls elements.startBtn.addEventListener('click', startWebcam); elements.captureBtn.addEventListener('click', capturePhoto); elements.retakeBtn.addEventListener('click', retakePhoto); elements.saveBtn.addEventListener('click', saveDocumentation); // Modal elements.closeModal.addEventListener('click', closeModal); elements.modal.addEventListener('click', (e) => { if (e.target === elements.modal) { closeModal(); } }); elements.printBtn.addEventListener('click', printDetail); elements.deleteBtn.addEventListener('click', deleteDetail); // Search elements.searchInput.addEventListener('input', (e) => { loadGallery(e.target.value); }); // Export elements.exportBtn.addEventListener('click', exportData); // Keyboard shortcuts document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { closeModal(); } }); } // ============================================ // INITIALIZATION // ============================================ async function init() { try { // Check API connection const isConnected = await checkApiConnection(); // Set default date setDefaultDate(); // Setup event listeners setupEventListeners(); // Load gallery await loadGallery(); console.log('✅ Aplikasi berhasil diinisialisasi'); } catch (error) { console.error('Error initializing app:', error); showToast('❌ Gagal menginisialisasi aplikasi', 'error'); } } // Start the app when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } // Make showDetail function globally accessible window.showDetail = showDetail;