One Hat Cyber Team
Your IP :
216.73.216.55
Server IP :
5.189.175.239
Server :
Linux panel.gemx-ai.com 5.14.0-570.19.1.el9_6.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jun 4 04:00:24 EDT 2025 x86_64
Server Software :
LiteSpeed
PHP Version :
8.2.28
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
home
/
farmersapp
/
loans.farmersapp.store
/
assets
/
js
/
Edit File:
main.js
// assets/js/main.js // Main JavaScript file for Farmers Loan Management System document.addEventListener('DOMContentLoaded', function() { // Initialize all components initSidebar(); initThemeToggle(); initNotifications(); initModals(); initForms(); initTables(); initCharts(); initToast(); initPasswordToggle(); initDatePickers(); initSelect2(); initDataTables(); initTooltips(); initClipboard(); initFileUpload(); initSearch(); initPrint(); initExport(); initAjaxForms(); initRealTimeUpdates(); initMobileMenu(); initScrollToTop(); initLazyLoad(); initPerformanceMonitor(); }); /** * Sidebar functionality */ function initSidebar() { const sidebar = document.querySelector('.sidebar'); const sidebarToggle = document.querySelector('.header-toggle'); const sidebarOverlay = document.createElement('div'); if (sidebar && sidebarToggle) { // Create overlay sidebarOverlay.className = 'sidebar-overlay'; sidebarOverlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 999; display: none; `; document.body.appendChild(sidebarOverlay); // Toggle sidebar sidebarToggle.addEventListener('click', function() { sidebar.classList.toggle('open'); sidebarOverlay.style.display = sidebar.classList.contains('open') ? 'block' : 'none'; }); // Close sidebar on overlay click sidebarOverlay.addEventListener('click', function() { sidebar.classList.remove('open'); sidebarOverlay.style.display = 'none'; }); // Close sidebar on escape key document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && sidebar.classList.contains('open')) { sidebar.classList.remove('open'); sidebarOverlay.style.display = 'none'; } }); // Handle submenu toggles const menuItemsWithSubmenu = document.querySelectorAll('.sidebar-menu-item.has-submenu'); menuItemsWithSubmenu.forEach(item => { const link = item.querySelector('.sidebar-menu-link'); const submenu = item.querySelector('.sidebar-submenu'); if (link && submenu) { link.addEventListener('click', function(e) { if (window.innerWidth < 992) { e.preventDefault(); submenu.classList.toggle('open'); } }); } }); } } /** * Theme toggle functionality */ function initThemeToggle() { const themeToggle = document.querySelector('.theme-toggle'); const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); if (themeToggle) { // Check for saved theme preference const currentTheme = localStorage.getItem('theme') || (prefersDarkScheme.matches ? 'dark' : 'light'); // Apply theme if (currentTheme === 'dark') { document.documentElement.setAttribute('data-theme', 'dark'); themeToggle.innerHTML = '<i class="fas fa-sun"></i>'; } else { document.documentElement.setAttribute('data-theme', 'light'); themeToggle.innerHTML = '<i class="fas fa-moon"></i>'; } // Toggle theme themeToggle.addEventListener('click', function() { const currentTheme = document.documentElement.getAttribute('data-theme'); let newTheme = 'light'; if (currentTheme === 'light') { newTheme = 'dark'; themeToggle.innerHTML = '<i class="fas fa-sun"></i>'; } else { themeToggle.innerHTML = '<i class="fas fa-moon"></i>'; } document.documentElement.setAttribute('data-theme', newTheme); localStorage.setItem('theme', newTheme); // Dispatch theme change event const event = new CustomEvent('themeChange', { detail: { theme: newTheme } }); document.dispatchEvent(event); }); } } /** * Notifications functionality */ function initNotifications() { const notificationBtn = document.querySelector('.header-notification-btn'); const notificationPanel = document.querySelector('.notification-panel'); if (notificationBtn && notificationPanel) { notificationBtn.addEventListener('click', function(e) { e.stopPropagation(); notificationPanel.classList.toggle('show'); // Mark notifications as read if (notificationPanel.classList.contains('show')) { markNotificationsAsRead(); } }); // Close notification panel when clicking outside document.addEventListener('click', function(e) { if (!notificationPanel.contains(e.target) && !notificationBtn.contains(e.target)) { notificationPanel.classList.remove('show'); } }); // Close on escape key document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && notificationPanel.classList.contains('show')) { notificationPanel.classList.remove('show'); } }); // Handle notification actions const notificationItems = notificationPanel.querySelectorAll('.notification-item'); notificationItems.forEach(item => { item.addEventListener('click', function() { const url = this.dataset.url; if (url) { window.location.href = url; } }); }); } // Real-time notification updates if (typeof EventSource !== 'undefined') { const eventSource = new EventSource('/api/notifications/stream'); eventSource.onmessage = function(event) { const notification = JSON.parse(event.data); showToast(notification.message, notification.type); updateNotificationBadge(); }; eventSource.onerror = function() { console.error('EventSource failed.'); eventSource.close(); }; } } /** * Mark notifications as read */ function markNotificationsAsRead() { fetch('/api/notifications/mark-read', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrfToken() } }) .then(response => response.json()) .then(data => { if (data.success) { updateNotificationBadge(); } }) .catch(error => console.error('Error:', error)); } /** * Update notification badge */ function updateNotificationBadge() { const badge = document.querySelector('.header-notification-badge'); if (badge) { fetch('/api/notifications/count') .then(response => response.json()) .then(data => { if (data.count > 0) { badge.textContent = data.count > 99 ? '99+' : data.count; badge.style.display = 'flex'; } else { badge.style.display = 'none'; } }) .catch(error => console.error('Error:', error)); } } /** * Modal functionality */ function initModals() { // Open modal document.querySelectorAll('[data-modal]').forEach(trigger => { trigger.addEventListener('click', function() { const modalId = this.dataset.modal; const modal = document.getElementById(modalId); if (modal) { modal.classList.add('open'); document.body.style.overflow = 'hidden'; // Focus first input in modal const firstInput = modal.querySelector('input, select, textarea'); if (firstInput) { setTimeout(() => firstInput.focus(), 100); } } }); }); // Close modal document.querySelectorAll('.modal-close, .btn-modal-close').forEach(closeBtn => { closeBtn.addEventListener('click', function() { const modal = this.closest('.modal'); if (modal) { modal.classList.remove('open'); document.body.style.overflow = ''; } }); }); // Close modal on overlay click document.querySelectorAll('.modal').forEach(modal => { modal.addEventListener('click', function(e) { if (e.target === this) { this.classList.remove('open'); document.body.style.overflow = ''; } }); }); // Close modal on escape key document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { document.querySelectorAll('.modal.open').forEach(modal => { modal.classList.remove('open'); document.body.style.overflow = ''; }); } }); } /** * Form validation and handling */ function initForms() { // Real-time form validation document.querySelectorAll('.form-control').forEach(input => { input.addEventListener('blur', function() { validateField(this); }); input.addEventListener('input', function() { if (this.classList.contains('is-invalid')) { validateField(this); } }); }); // Form submission document.querySelectorAll('form').forEach(form => { form.addEventListener('submit', function(e) { if (!validateForm(this)) { e.preventDefault(); showToast('Please fix the errors in the form', 'error'); } }); }); // Dynamic form fields document.querySelectorAll('.btn-add-field').forEach(button => { button.addEventListener('click', function() { const template = document.getElementById(this.dataset.template); const container = document.getElementById(this.dataset.container); if (template && container) { const clone = template.content.cloneNode(true); const index = container.children.length; // Update field names and IDs clone.querySelectorAll('[name], [id]').forEach(element => { if (element.name) { element.name = element.name.replace('[]', `[${index}]`); } if (element.id) { element.id = element.id + '_' + index; } if (element.htmlFor) { element.htmlFor = element.htmlFor + '_' + index; } }); container.appendChild(clone); // Initialize new fields initFormFields(clone); } }); }); // Remove dynamic fields document.addEventListener('click', function(e) { if (e.target.classList.contains('btn-remove-field')) { const field = e.target.closest('.dynamic-field'); if (field) { field.remove(); } } }); } /** * Validate form field */ function validateField(field) { const value = field.value.trim(); const isValid = true; const errorMessages = []; // Required validation if (field.required && !value) { errorMessages.push('This field is required'); } // Email validation if (field.type === 'email' && value) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(value)) { errorMessages.push('Please enter a valid email address'); } } // Phone validation if (field.type === 'tel' && value) { const phoneRegex = /^[\+]?[0-9\s\-\(\)]+$/; if (!phoneRegex.test(value)) { errorMessages.push('Please enter a valid phone number'); } } // Number validation if (field.type === 'number' && value) { const min = parseFloat(field.min); const max = parseFloat(field.max); if (!isNaN(min) && parseFloat(value) < min) { errorMessages.push(`Value must be at least ${min}`); } if (!isNaN(max) && parseFloat(value) > max) { errorMessages.push(`Value must be at most ${max}`); } } // Pattern validation if (field.pattern && value) { const regex = new RegExp(field.pattern); if (!regex.test(value)) { errorMessages.push(field.title || 'Please match the requested format'); } } // Update field state const feedback = field.nextElementSibling; if (errorMessages.length > 0) { field.classList.add('is-invalid'); field.classList.remove('is-valid'); if (feedback && feedback.classList.contains('invalid-feedback')) { feedback.textContent = errorMessages[0]; } } else if (value) { field.classList.remove('is-invalid'); field.classList.add('is-valid'); } else { field.classList.remove('is-invalid', 'is-valid'); } return errorMessages.length === 0; } /** * Validate entire form */ function validateForm(form) { let isValid = true; const fields = form.querySelectorAll('.form-control'); fields.forEach(field => { if (!validateField(field)) { isValid = false; } }); return isValid; } /** * Initialize form fields */ function initFormFields(container) { container.querySelectorAll('.form-control').forEach(input => { input.addEventListener('blur', function() { validateField(this); }); }); } /** * Table functionality */ function initTables() { // Sortable tables document.querySelectorAll('.table th[data-sort]').forEach(header => { header.style.cursor = 'pointer'; header.addEventListener('click', function() { const table = this.closest('table'); const column = this.dataset.sort; const direction = this.dataset.sortDirection === 'asc' ? 'desc' : 'asc'; // Update sort direction this.dataset.sortDirection = direction; // Remove sort indicators from other headers table.querySelectorAll('th[data-sort]').forEach(th => { if (th !== this) { delete th.dataset.sortDirection; } }); // Sort table sortTable(table, column, direction); }); }); // Row selection document.querySelectorAll('.table tbody tr[data-id]').forEach(row => { row.addEventListener('click', function(e) { if (!e.target.closest('a, button, input, select, textarea')) { this.classList.toggle('selected'); } }); }); // Bulk actions const bulkActions = document.querySelector('.bulk-actions'); if (bulkActions) { const selectAll = bulkActions.querySelector('.select-all'); const selectedCount = bulkActions.querySelector('.selected-count'); if (selectAll) { selectAll.addEventListener('change', function() { const checkboxes = document.querySelectorAll('.row-select:not(:disabled)'); checkboxes.forEach(checkbox => { checkbox.checked = this.checked; const row = checkbox.closest('tr'); if (row) { row.classList.toggle('selected', this.checked); } }); updateSelectedCount(); }); } // Update selected count document.addEventListener('change', function(e) { if (e.target.classList.contains('row-select')) { updateSelectedCount(); } }); function updateSelectedCount() { if (selectedCount) { const selected = document.querySelectorAll('.row-select:checked').length; selectedCount.textContent = selected; bulkActions.classList.toggle('has-selection', selected > 0); } } } } /** * Sort table */ function sortTable(table, column, direction) { const tbody = table.querySelector('tbody'); const rows = Array.from(tbody.querySelectorAll('tr')); rows.sort((a, b) => { const aValue = a.querySelector(`td[data-column="${column}"]`)?.textContent || ''; const bValue = b.querySelector(`td[data-column="${column}"]`)?.textContent || ''; // Try to compare as numbers const aNum = parseFloat(aValue.replace(/[^\d.-]/g, '')); const bNum = parseFloat(bValue.replace(/[^\d.-]/g, '')); if (!isNaN(aNum) && !isNaN(bNum)) { return direction === 'asc' ? aNum - bNum : bNum - aNum; } // Compare as strings return direction === 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue); }); // Reorder rows rows.forEach(row => tbody.appendChild(row)); } /** * Chart initialization */ function initCharts() { // Loan statistics chart const loanChartEl = document.getElementById('loanChart'); if (loanChartEl) { const ctx = loanChartEl.getContext('2d'); new Chart(ctx, { type: 'line', data: { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], datasets: [{ label: 'Loans Disbursed', data: [12000, 19000, 15000, 25000, 22000, 30000, 28000, 35000, 32000, 40000, 38000, 45000], borderColor: '#3498db', backgroundColor: 'rgba(52, 152, 219, 0.1)', borderWidth: 2, fill: true, tension: 0.4 }, { label: 'Loans Repaid', data: [8000, 12000, 10000, 18000, 15000, 22000, 20000, 28000, 25000, 32000, 30000, 38000], borderColor: '#27ae60', backgroundColor: 'rgba(39, 174, 96, 0.1)', borderWidth: 2, fill: true, tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'top', }, tooltip: { mode: 'index', intersect: false, callbacks: { label: function(context) { return `${context.dataset.label}: MWK ${context.parsed.y.toLocaleString()}`; } } } }, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return 'MWK ' + value.toLocaleString(); } } } } } }); } // Portfolio distribution chart const portfolioChartEl = document.getElementById('portfolioChart'); if (portfolioChartEl) { const ctx = portfolioChartEl.getContext('2d'); new Chart(ctx, { type: 'doughnut', data: { labels: ['Agriculture', 'Small Business', 'Education', 'Emergency', 'Other'], datasets: [{ data: [40, 25, 15, 10, 10], backgroundColor: [ '#3498db', '#2ecc71', '#e74c3c', '#f39c12', '#9b59b6' ], borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', }, tooltip: { callbacks: { label: function(context) { return `${context.label}: ${context.parsed}%`; } } } } } }); } // Performance chart const performanceChartEl = document.getElementById('performanceChart'); if (performanceChartEl) { const ctx = performanceChartEl.getContext('2d'); new Chart(ctx, { type: 'bar', data: { labels: ['Q1', 'Q2', 'Q3', 'Q4'], datasets: [{ label: 'Portfolio at Risk', data: [5.2, 4.8, 3.9, 2.7], backgroundColor: '#e74c3c', borderColor: '#c0392b', borderWidth: 1 }, { label: 'Collection Rate', data: [92, 94, 96, 98], backgroundColor: '#27ae60', borderColor: '#229954', borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return value + '%'; } } } }, plugins: { tooltip: { callbacks: { label: function(context) { return `${context.dataset.label}: ${context.parsed.y}%`; } } } } } }); } } /** * Toast notifications */ function initToast() { window.showToast = function(message, type = 'info', duration = 5000) { const container = document.querySelector('.toast-container') || createToastContainer(); const toast = createToast(message, type); container.appendChild(toast); // Auto remove after duration setTimeout(() => { toast.style.animation = 'slideOutRight 0.3s ease'; setTimeout(() => toast.remove(), 300); }, duration); // Remove on click toast.addEventListener('click', function() { this.style.animation = 'slideOutRight 0.3s ease'; setTimeout(() => this.remove(), 300); }); }; function createToastContainer() { const container = document.createElement('div'); container.className = 'toast-container'; document.body.appendChild(container); return container; } function createToast(message, type) { const toast = document.createElement('div'); toast.className = `toast ${type}`; const icons = { success: 'fas fa-check-circle', error: 'fas fa-exclamation-circle', warning: 'fas fa-exclamation-triangle', info: 'fas fa-info-circle' }; toast.innerHTML = ` <div class="toast-icon"> <i class="${icons[type] || icons.info}"></i> </div> <div class="toast-content"> <div class="toast-title">${type.charAt(0).toUpperCase() + type.slice(1)}</div> <p class="toast-message">${message}</p> </div> <button class="toast-close"> <i class="fas fa-times"></i> </button> `; return toast; } } /** * Password toggle visibility */ function initPasswordToggle() { document.querySelectorAll('.password-toggle').forEach(toggle => { toggle.addEventListener('click', function() { const input = this.previousElementSibling; const icon = this.querySelector('i'); if (input.type === 'password') { input.type = 'text'; icon.classList.remove('fa-eye'); icon.classList.add('fa-eye-slash'); } else { input.type = 'password'; icon.classList.remove('fa-eye-slash'); icon.classList.add('fa-eye'); } }); }); } /** * Date pickers */ function initDatePickers() { if (typeof flatpickr !== 'undefined') { document.querySelectorAll('.datepicker').forEach(input => { flatpickr(input, { dateFormat: 'Y-m-d', allowInput: true, disableMobile: true, locale: { firstDayOfWeek: 1 } }); }); document.querySelectorAll('.datetimepicker').forEach(input => { flatpickr(input, { enableTime: true, dateFormat: 'Y-m-d H:i', allowInput: true, disableMobile: true }); }); } } /** * Select2 initialization */ function initSelect2() { if (typeof $.fn.select2 !== 'undefined') { $('.select2').select2({ width: '100%', theme: 'bootstrap-5', placeholder: 'Select an option', allowClear: true }); $('.select2-tags').select2({ tags: true, width: '100%', theme: 'bootstrap-5', placeholder: 'Add tags' }); $('.select2-ajax').each(function() { const $this = $(this); $this.select2({ ajax: { url: $this.data('url'), dataType: 'json', delay: 250, data: function(params) { return { q: params.term, page: params.page || 1 }; }, processResults: function(data) { return { results: data.results, pagination: { more: data.more } }; }, cache: true }, width: '100%', theme: 'bootstrap-5', minimumInputLength: 1 }); }); } } /** * DataTables initialization */ function initDataTables() { if (typeof $.fn.DataTable !== 'undefined') { $('.datatable').DataTable({ responsive: true, pageLength: 25, lengthMenu: [[10, 25, 50, 100, -1], [10, 25, 50, 100, 'All']], language: { search: '_INPUT_', searchPlaceholder: 'Search...', lengthMenu: '_MENU_ records per page', info: 'Showing _START_ to _END_ of _TOTAL_ entries', infoEmpty: 'Showing 0 to 0 of 0 entries', infoFiltered: '(filtered from _MAX_ total entries)', zeroRecords: 'No matching records found', paginate: { first: 'First', last: 'Last', next: 'Next', previous: 'Previous' } }, dom: '<"row"<"col-sm-12 col-md-6"l><"col-sm-12 col-md-6"f>>' + '<"row"<"col-sm-12"tr>>' + '<"row"<"col-sm-12 col-md-5"i><"col-sm-12 col-md-7"p>>', initComplete: function() { // Add custom buttons const api = this.api(); const buttons = '<div class="dt-buttons btn-group">' + '<button class="btn btn-secondary btn-sm btn-export" data-type="csv">CSV</button>' + '<button class="btn btn-secondary btn-sm btn-export" data-type="excel">Excel</button>' + '<button class="btn btn-secondary btn-sm btn-export" data-type="pdf">PDF</button>' + '<button class="btn btn-secondary btn-sm btn-print">Print</button>' + '</div>'; $('.dataTables_length').before(buttons); // Export buttons $('.btn-export').click(function() { const type = $(this).data('type'); exportTable(api, type); }); // Print button $('.btn-print').click(function() { printTable(api); }); } }); } } /** * Export table data */ function exportTable(api, type) { const data = api.data().toArray(); const headers = api.columns().header().toArray().map(th => th.textContent); switch(type) { case 'csv': exportCSV(data, headers); break; case 'excel': exportExcel(data, headers); break; case 'pdf': exportPDF(data, headers); break; } } /** * Export to CSV */ function exportCSV(data, headers) { let csv = headers.join(',') + '\n'; data.forEach(row => { csv += row.map(cell => { // Escape quotes and wrap in quotes if contains comma const text = String(cell).replace(/"/g, '""'); return text.includes(',') ? `"${text}"` : text; }).join(',') + '\n'; }); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.setAttribute('href', url); link.setAttribute('download', `export-${new Date().toISOString().slice(0,10)}.csv`); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } /** * Export to Excel */ function exportExcel(data, headers) { // Using SheetJS if available if (typeof XLSX !== 'undefined') { const ws = XLSX.utils.aoa_to_sheet([headers, ...data]); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, 'Sheet1'); XLSX.writeFile(wb, `export-${new Date().toISOString().slice(0,10)}.xlsx`); } else { showToast('Excel export requires SheetJS library', 'warning'); } } /** * Export to PDF */ function exportPDF(data, headers) { // Using jsPDF if available if (typeof jsPDF !== 'undefined') { const doc = new jsPDF(); doc.autoTable({ head: [headers], body: data, theme: 'grid', styles: { fontSize: 8, cellPadding: 2 }, headStyles: { fillColor: [52, 152, 219], textColor: 255, fontSize: 9, fontStyle: 'bold' } }); doc.save(`export-${new Date().toISOString().slice(0,10)}.pdf`); } else { showToast('PDF export requires jsPDF library', 'warning'); } } /** * Print table */ function printTable(api) { const printWindow = window.open('', '_blank'); const title = document.title; const headers = api.columns().header().toArray().map(th => th.textContent); const data = api.data().toArray(); printWindow.document.write(` <html> <head> <title>${title}</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } h1 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; } table { width: 100%; border-collapse: collapse; margin-top: 20px; } th { background: #3498db; color: white; padding: 10px; text-align: left; } td { padding: 8px; border-bottom: 1px solid #ddd; } tr:nth-child(even) { background: #f8f9fa; } .print-date { color: #666; margin-bottom: 20px; } @media print { body { margin: 0; } .no-print { display: none; } } </style> </head> <body> <h1>${title}</h1> <div class="print-date">Printed: ${new Date().toLocaleString()}</div> <table> <thead> <tr> ${headers.map(h => `<th>${h}</th>`).join('')} </tr> </thead> <tbody> ${data.map(row => ` <tr> ${row.map(cell => `<td>${cell}</td>`).join('')} </tr> `).join('')} </tbody> </table> <div class="no-print" style="margin-top: 20px;"> <button onclick="window.print()">Print</button> <button onclick="window.close()">Close</button> </div> </body> </html> `); printWindow.document.close(); } /** * Tooltips */ function initTooltips() { if (typeof $.fn.tooltip !== 'undefined') { $('[data-toggle="tooltip"]').tooltip({ trigger: 'hover', placement: 'top' }); } else if (typeof tippy !== 'undefined') { tippy('[data-tippy-content]', { arrow: true, animation: 'shift-away', duration: 200 }); } } /** * Clipboard */ function initClipboard() { if (typeof ClipboardJS !== 'undefined') { new ClipboardJS('.btn-copy', { text: function(trigger) { return trigger.getAttribute('data-clipboard-text') || trigger.previousElementSibling?.value || trigger.closest('.copyable')?.textContent; } }).on('success', function(e) { showToast('Copied to clipboard', 'success'); e.clearSelection(); }).on('error', function(e) { showToast('Failed to copy', 'error'); }); } } /** * File upload */ function initFileUpload() { document.querySelectorAll('.file-upload').forEach(input => { const preview = document.getElementById(input.dataset.preview); const removeBtn = input.nextElementSibling?.querySelector('.file-remove'); if (preview) { input.addEventListener('change', function() { const file = this.files[0]; if (file) { if (file.type.startsWith('image/')) { const reader = new FileReader(); reader.onload = function(e) { preview.src = e.target.result; preview.style.display = 'block'; }; reader.readAsDataURL(file); } else { preview.style.display = 'none'; } } }); } if (removeBtn) { removeBtn.addEventListener('click', function() { input.value = ''; if (preview) { preview.style.display = 'none'; } }); } }); // Drag and drop const dropzones = document.querySelectorAll('.dropzone'); dropzones.forEach(dropzone => { ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { dropzone.addEventListener(eventName, preventDefaults, false); }); function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } ['dragenter', 'dragover'].forEach(eventName => { dropzone.addEventListener(eventName, highlight, false); }); ['dragleave', 'drop'].forEach(eventName => { dropzone.addEventListener(eventName, unhighlight, false); }); function highlight() { dropzone.classList.add('highlight'); } function unhighlight() { dropzone.classList.remove('highlight'); } dropzone.addEventListener('drop', handleDrop, false); function handleDrop(e) { const dt = e.dataTransfer; const files = dt.files; const input = dropzone.querySelector('input[type="file"]'); if (input && files.length) { input.files = files; input.dispatchEvent(new Event('change')); } } }); } /** * Search functionality */ function initSearch() { const searchInput = document.querySelector('.search-input'); const searchResults = document.querySelector('.search-results'); if (searchInput && searchResults) { let searchTimeout; searchInput.addEventListener('input', function() { clearTimeout(searchTimeout); const query = this.value.trim(); if (query.length < 2) { searchResults.style.display = 'none'; return; } searchTimeout = setTimeout(() => { performSearch(query); }, 300); }); // Close results on click outside document.addEventListener('click', function(e) { if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) { searchResults.style.display = 'none'; } }); // Close on escape document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { searchResults.style.display = 'none'; } }); } function performSearch(query) { fetch(`/api/search?q=${encodeURIComponent(query)}`) .then(response => response.json()) .then(data => { displaySearchResults(data); }) .catch(error => { console.error('Search error:', error); }); } function displaySearchResults(results) { const searchResults = document.querySelector('.search-results'); if (!searchResults) return; if (results.length === 0) { searchResults.innerHTML = '<div class="search-empty">No results found</div>'; searchResults.style.display = 'block'; return; } let html = ''; results.forEach(result => { html += ` <a href="${result.url}" class="search-result"> <div class="search-result-icon"> <i class="${result.icon}"></i> </div> <div class="search-result-content"> <div class="search-result-title">${result.title}</div> <div class="search-result-description">${result.description}</div> </div> </a> `; }); searchResults.innerHTML = html; searchResults.style.display = 'block'; } } /** * Print functionality */ function initPrint() { document.querySelectorAll('.btn-print').forEach(button => { button.addEventListener('click', function() { const element = document.getElementById(this.dataset.print) || this.closest('.printable') || document.body; const originalStyles = {}; const elementsToHide = element.querySelectorAll('.no-print'); // Hide elements with no-print class elementsToHide.forEach(el => { originalStyles[el] = el.style.display; el.style.display = 'none'; }); // Print window.print(); // Restore original styles elementsToHide.forEach(el => { el.style.display = originalStyles[el]; }); }); }); } /** * Export functionality */ function initExport() { document.querySelectorAll('.btn-export').forEach(button => { button.addEventListener('click', function() { const type = this.dataset.type || 'csv'; const element = document.getElementById(this.dataset.export) || this.closest('.exportable'); if (element) { exportElement(element, type); } }); }); function exportElement(element, type) { const table = element.querySelector('table'); if (table) { const headers = Array.from(table.querySelectorAll('th')).map(th => th.textContent); const rows = Array.from(table.querySelectorAll('tbody tr')).map(tr => Array.from(tr.querySelectorAll('td')).map(td => td.textContent) ); switch(type) { case 'csv': exportCSV(rows, headers); break; case 'excel': exportExcel(rows, headers); break; case 'pdf': exportPDF(rows, headers); break; } } } } /** * AJAX form submissions */ function initAjaxForms() { document.querySelectorAll('form[data-ajax]').forEach(form => { form.addEventListener('submit', function(e) { e.preventDefault(); const submitBtn = this.querySelector('[type="submit"]'); const originalText = submitBtn?.textContent; const formData = new FormData(this); // Show loading state if (submitBtn) { submitBtn.disabled = true; submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing...'; } fetch(this.action, { method: this.method, body: formData, headers: { 'X-Requested-With': 'XMLHttpRequest' } }) .then(response => response.json()) .then(data => { if (data.success) { showToast(data.message || 'Operation successful', 'success'); // Redirect if specified if (data.redirect) { setTimeout(() => { window.location.href = data.redirect; }, 1500); } // Reset form if specified if (data.reset) { this.reset(); } // Reload page if specified if (data.reload) { setTimeout(() => { window.location.reload(); }, 1500); } // Call callback if specified if (data.callback) { if (typeof window[data
Simpan