/** * Main JavaScript for Kayal Aqua Fish Business Management * Mobile-First Responsive Application */ class KayalAquaApp { constructor() { this.init(); } init() { this.setupEventListeners(); this.initializeComponents(); this.handleResponsiveLayout(); } setupEventListeners() { // Navigation toggle document.addEventListener('click', (e) => { if (e.target.matches('.nav-toggle') || e.target.closest('.nav-toggle')) { this.toggleMobileNav(); } if (e.target.matches('.nav-close') || e.target.closest('.nav-close')) { this.closeMobileNav(); } if (e.target.matches('.nav-overlay')) { this.closeMobileNav(); } // Modal handlers if (e.target.matches('[data-modal]')) { this.openModal(e.target.dataset.modal); } if (e.target.matches('.modal-close') || e.target.matches('.modal-overlay')) { this.closeModal(); } // Dropdown handlers if (e.target.matches('.dropdown-toggle') || e.target.closest('.dropdown-toggle')) { this.toggleDropdown(e.target.closest('.dropdown')); } // Tab handlers if (e.target.matches('.tab-nav-item')) { this.switchTab(e.target); } // Accordion handlers if (e.target.matches('.accordion-header') || e.target.closest('.accordion-header')) { this.toggleAccordion(e.target.closest('.accordion-header')); } }); // Close dropdowns when clicking outside document.addEventListener('click', (e) => { if (!e.target.closest('.dropdown')) { this.closeAllDropdowns(); } }); // Form submissions document.addEventListener('submit', (e) => { if (e.target.matches('form[data-ajax]')) { e.preventDefault(); this.handleAjaxForm(e.target); } }); // Window resize handler window.addEventListener('resize', () => { this.handleResponsiveLayout(); }); // Scroll handlers for mobile let lastScrollTop = 0; window.addEventListener('scroll', () => { const scrollTop = window.pageYOffset || document.documentElement.scrollTop; // Hide/show header on scroll (mobile) if (window.innerWidth <= 768) { const header = document.querySelector('.header'); if (header) { if (scrollTop > lastScrollTop && scrollTop > 100) { header.style.transform = 'translateY(-100%)'; } else { header.style.transform = 'translateY(0)'; } } } lastScrollTop = scrollTop <= 0 ? 0 : scrollTop; }); } initializeComponents() { // Initialize tooltips this.initializeTooltips(); // Initialize form validation this.initializeFormValidation(); // Initialize data tables this.initializeDataTables(); // Initialize charts if needed this.initializeCharts(); // Set active navigation this.setActiveNavigation(); } // Navigation Methods toggleMobileNav() { const nav = document.querySelector('.main-nav'); const overlay = document.querySelector('.nav-overlay'); if (nav && overlay) { nav.classList.toggle('active'); overlay.classList.toggle('active'); document.body.style.overflow = nav.classList.contains('active') ? 'hidden' : ''; } } closeMobileNav() { const nav = document.querySelector('.main-nav'); const overlay = document.querySelector('.nav-overlay'); if (nav && overlay) { nav.classList.remove('active'); overlay.classList.remove('active'); document.body.style.overflow = ''; } } setActiveNavigation() { const currentPage = window.location.pathname.split('/').pop() || 'index.php'; const navLinks = document.querySelectorAll('.nav-link'); navLinks.forEach(link => { const href = link.getAttribute('href'); if (href === currentPage || (currentPage === '' && href === 'index.php')) { link.classList.add('active'); } else { link.classList.remove('active'); } }); } // Modal Methods openModal(modalId) { const modal = document.getElementById(modalId); if (modal) { modal.classList.add('active'); document.body.style.overflow = 'hidden'; } } closeModal() { const activeModal = document.querySelector('.modal.active'); if (activeModal) { activeModal.classList.remove('active'); document.body.style.overflow = ''; } } // Dropdown Methods toggleDropdown(dropdown) { if (dropdown) { dropdown.classList.toggle('active'); } } closeAllDropdowns() { const dropdowns = document.querySelectorAll('.dropdown.active'); dropdowns.forEach(dropdown => { dropdown.classList.remove('active'); }); } // Tab Methods switchTab(tabButton) { const tabContainer = tabButton.closest('.tabs'); if (!tabContainer) return; const targetTab = tabButton.dataset.tab; // Remove active class from all tabs and contents tabContainer.querySelectorAll('.tab-nav-item').forEach(tab => { tab.classList.remove('active'); }); tabContainer.querySelectorAll('.tab-content').forEach(content => { content.classList.remove('active'); }); // Add active class to clicked tab and corresponding content tabButton.classList.add('active'); const targetContent = tabContainer.querySelector(`#${targetTab}`); if (targetContent) { targetContent.classList.add('active'); } } // Accordion Methods toggleAccordion(header) { const content = header.nextElementSibling; const icon = header.querySelector('.accordion-icon'); if (content) { header.classList.toggle('active'); content.classList.toggle('active'); if (icon) { icon.style.transform = content.classList.contains('active') ? 'rotate(180deg)' : 'rotate(0deg)'; } } } // Form Methods handleAjaxForm(form) { const formData = new FormData(form); const url = form.action || window.location.href; this.showLoader(); fetch(url, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { this.hideLoader(); if (data.success) { this.showAlert(data.message || 'Operation completed successfully', 'success'); if (data.redirect) { setTimeout(() => { window.location.href = data.redirect; }, 1500); } if (data.reload) { setTimeout(() => { window.location.reload(); }, 1500); } } else { this.showAlert(data.message || 'An error occurred', 'danger'); } }) .catch(error => { this.hideLoader(); this.showAlert('Network error occurred', 'danger'); console.error('Error:', error); }); } initializeFormValidation() { const forms = document.querySelectorAll('form[data-validate]'); forms.forEach(form => { form.addEventListener('submit', (e) => { if (!this.validateForm(form)) { e.preventDefault(); } }); // Real-time validation const inputs = form.querySelectorAll('input, select, textarea'); inputs.forEach(input => { input.addEventListener('blur', () => { this.validateField(input); }); }); }); } validateForm(form) { let isValid = true; const inputs = form.querySelectorAll('[required], [data-validate]'); inputs.forEach(input => { if (!this.validateField(input)) { isValid = false; } }); return isValid; } validateField(field) { const value = field.value.trim(); const type = field.type; let isValid = true; let message = ''; // Clear previous errors this.clearFieldError(field); // Required validation if (field.hasAttribute('required') && !value) { isValid = false; message = 'This field is required'; } // Email validation if (type === 'email' && value && !this.isValidEmail(value)) { isValid = false; message = 'Please enter a valid email address'; } // Phone validation if (field.dataset.validate === 'phone' && value && !this.isValidPhone(value)) { isValid = false; message = 'Please enter a valid phone number'; } // Number validation if (type === 'number' && value) { const min = parseFloat(field.min); const max = parseFloat(field.max); const numValue = parseFloat(value); if (!isNaN(min) && numValue < min) { isValid = false; message = `Value must be at least ${min}`; } if (!isNaN(max) && numValue > max) { isValid = false; message = `Value must be no more than ${max}`; } } if (!isValid) { this.showFieldError(field, message); } return isValid; } showFieldError(field, message) { field.classList.add('is-invalid'); let errorDiv = field.parentNode.querySelector('.invalid-feedback'); if (!errorDiv) { errorDiv = document.createElement('div'); errorDiv.className = 'invalid-feedback'; field.parentNode.appendChild(errorDiv); } errorDiv.textContent = message; } clearFieldError(field) { field.classList.remove('is-invalid'); const errorDiv = field.parentNode.querySelector('.invalid-feedback'); if (errorDiv) { errorDiv.remove(); } } // Utility Methods isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } isValidPhone(phone) { const phoneRegex = /^[+]?[0-9\s\-\(\)]{10,}$/; return phoneRegex.test(phone); } showAlert(message, type = 'info', duration = 5000) { const alertContainer = document.getElementById('alert-container') || this.createAlertContainer(); const alert = document.createElement('div'); alert.className = `alert alert-${type} alert-dismissible`; alert.innerHTML = ` ${message} `; alertContainer.appendChild(alert); // Auto remove after duration if (duration > 0) { setTimeout(() => { if (alert.parentNode) { alert.remove(); } }, duration); } } createAlertContainer() { const container = document.createElement('div'); container.id = 'alert-container'; container.className = 'alert-container'; container.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 9999; max-width: 400px; `; document.body.appendChild(container); return container; } showLoader() { let loader = document.getElementById('app-loader'); if (!loader) { loader = document.createElement('div'); loader.id = 'app-loader'; loader.innerHTML = '
'; loader.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.8); display: flex; justify-content: center; align-items: center; z-index: 9999; `; document.body.appendChild(loader); } loader.style.display = 'flex'; } hideLoader() { const loader = document.getElementById('app-loader'); if (loader) { loader.style.display = 'none'; } } initializeTooltips() { // Tooltip functionality is handled by CSS :hover // This method can be extended for more complex tooltip behavior } initializeDataTables() { const tables = document.querySelectorAll('.data-table[data-sortable]'); tables.forEach(table => { this.makeTableSortable(table); }); } makeTableSortable(table) { const headers = table.querySelectorAll('th[data-sortable]'); headers.forEach(header => { header.style.cursor = 'pointer'; header.addEventListener('click', () => { this.sortTable(table, header); }); }); } sortTable(table, header) { const tbody = table.querySelector('tbody'); const rows = Array.from(tbody.querySelectorAll('tr')); const columnIndex = Array.from(header.parentNode.children).indexOf(header); const isAscending = !header.classList.contains('sort-asc'); // Clear all sort classes header.parentNode.querySelectorAll('th').forEach(th => { th.classList.remove('sort-asc', 'sort-desc'); }); // Add sort class to current header header.classList.add(isAscending ? 'sort-asc' : 'sort-desc'); // Sort rows rows.sort((a, b) => { const aValue = a.children[columnIndex].textContent.trim(); const bValue = b.children[columnIndex].textContent.trim(); // Try to parse as numbers const aNum = parseFloat(aValue.replace(/[^\d.-]/g, '')); const bNum = parseFloat(bValue.replace(/[^\d.-]/g, '')); if (!isNaN(aNum) && !isNaN(bNum)) { return isAscending ? aNum - bNum : bNum - aNum; } // String comparison return isAscending ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue); }); // Reorder DOM rows.forEach(row => tbody.appendChild(row)); } initializeCharts() { // Chart initialization will be handled in charts.js // This method triggers chart initialization if charts.js is loaded if (typeof initializeCharts === 'function') { initializeCharts(); } } handleResponsiveLayout() { const isMobile = window.innerWidth <= 768; // Handle table responsiveness const tables = document.querySelectorAll('.table'); tables.forEach(table => { if (isMobile) { table.classList.add('table-mobile'); } else { table.classList.remove('table-mobile'); } }); // Close mobile nav on resize to desktop if (!isMobile) { this.closeMobileNav(); } } // API Methods async apiCall(url, method = 'GET', data = null) { const options = { method, headers: { 'Content-Type': 'application/json', } }; if (data) { options.body = JSON.stringify(data); } try { const response = await fetch(url, options); return await response.json(); } catch (error) { console.error('API Error:', error); throw error; } } // Format currency for display formatCurrency(amount) { return new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR', minimumFractionDigits: 2 }).format(amount); } // Format date for display formatDate(dateString, options = {}) { const defaultOptions = { year: 'numeric', month: 'short', day: 'numeric' }; return new Date(dateString).toLocaleDateString('en-IN', { ...defaultOptions, ...options }); } // Debounce function for search inputs debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } } // Initialize the application when DOM is ready document.addEventListener('DOMContentLoaded', () => { window.kayalAquaApp = new KayalAquaApp(); }); // Export for use in other modules if (typeof module !== 'undefined' && module.exports) { module.exports = KayalAquaApp; }