// WiFi Attendance Tracker - Advanced JavaScript class AttendanceTracker { constructor() { this.isMonitoring = false; this.updateInterval = null; this.employees = []; this.events = []; this.summary = []; this.searchQuery = ''; this.init(); } init() { this.setupEventListeners(); this.loadInitialData(); this.startAutoUpdate(); this.updateCurrentTime(); // Set today's date in summary date input const today = new Date().toISOString().split('T')[0]; document.getElementById('summaryDate').value = today; } setupEventListeners() { // Control buttons document.getElementById('startBtn').addEventListener('click', () => this.startMonitoring()); document.getElementById('stopBtn').addEventListener('click', () => this.stopMonitoring()); document.getElementById('refreshBtn').addEventListener('click', () => this.refreshData()); document.getElementById('exportBtn').addEventListener('click', () => this.exportCSV()); // Search functionality document.getElementById('searchBtn').addEventListener('click', () => this.performSearch()); document.getElementById('clearSearchBtn').addEventListener('click', () => this.clearSearch()); document.getElementById('searchInput').addEventListener('keypress', (e) => { if (e.key === 'Enter') this.performSearch(); }); document.getElementById('searchInput').addEventListener('input', (e) => { if (e.target.value === '') this.clearSearch(); }); // Modal buttons document.getElementById('addEmployeeBtn').addEventListener('click', () => this.openModal('addEmployeeModal')); document.getElementById('settingsBtn').addEventListener('click', () => this.openModal('settingsModal')); // Form submissions document.getElementById('addEmployeeForm').addEventListener('submit', (e) => this.handleAddEmployee(e)); document.getElementById('changePasswordForm').addEventListener('submit', (e) => this.handleChangePassword(e)); document.getElementById('deleteEmployeeForm').addEventListener('submit', (e) => this.handleDeleteEmployee(e)); document.getElementById('modifyEmployeeForm').addEventListener('submit', (e) => this.handleModifyEmployee(e)); // Summary date change document.getElementById('summaryDate').addEventListener('change', () => this.loadDailySummary()); document.getElementById('summaryRefreshBtn').addEventListener('click', () => this.loadDailySummary()); // Event date filter document.getElementById('eventDateFilter').addEventListener('change', () => this.loadEvents()); // Modal close buttons document.querySelectorAll('.modal-close').forEach(btn => { btn.addEventListener('click', (e) => { const modal = e.target.closest('.modal'); this.closeModal(modal.id); }); }); // Close modal when clicking outside document.querySelectorAll('.modal').forEach(modal => { modal.addEventListener('click', (e) => { if (e.target === modal) { this.closeModal(modal.id); } }); }); } async loadInitialData() { await Promise.all([ this.loadSystemStatus(), this.loadEmployees(), this.loadEvents(), this.loadDailySummary(), this.loadSummaryStats() ]); } async loadSystemStatus() { try { const response = await fetch('/api/status'); const status = await response.json(); document.getElementById('systemStatus').textContent = status.is_monitoring ? 'Monitoring' : 'Stopped'; document.getElementById('systemStatus').className = `status-value ${status.is_monitoring ? 'monitoring' : 'stopped'}`; document.getElementById('employeeCount').textContent = status.employee_count; document.getElementById('scanInterval').textContent = `${status.scan_interval} seconds`; document.getElementById('officeTimeout').textContent = status.office_timeout; this.isMonitoring = status.is_monitoring; this.updateControlButtons(); } catch (error) { console.error('Error loading system status:', error); this.showNotification('Error loading system status', 'error'); } } async loadEmployees() { try { const response = await fetch('/api/employees'); this.employees = await response.json(); this.renderEmployees(); } catch (error) { console.error('Error loading employees:', error); this.showNotification('Error loading employees', 'error'); } } async loadEvents() { try { const dateFilter = document.getElementById('eventDateFilter').value; const url = dateFilter ? `/api/attendance_events?date=${dateFilter}&limit=50` : '/api/attendance_events?limit=50'; const response = await fetch(url); this.events = await response.json(); this.renderEvents(); } catch (error) { console.error('Error loading events:', error); this.showNotification('Error loading events', 'error'); } } async loadDailySummary() { try { const date = document.getElementById('summaryDate').value; const response = await fetch(`/api/daily_summary?date=${date}`); this.summary = await response.json(); this.renderDailySummary(); } catch (error) { console.error('Error loading daily summary:', error); this.showNotification('Error loading daily summary', 'error'); } } async loadSummaryStats() { try { const date = document.getElementById('summaryDate').value; const response = await fetch(`/api/summary_stats?date=${date}`); const stats = await response.json(); document.getElementById('presentCount').textContent = stats.present_count || 0; document.getElementById('absentCount').textContent = stats.absent_count || 0; document.getElementById('breakCount').textContent = stats.on_break_count || 0; document.getElementById('timeoutCount').textContent = stats.timed_out_count || 0; } catch (error) { console.error('Error loading summary stats:', error); } } renderEmployees() { const container = document.getElementById('employeeList'); if (this.employees.length === 0) { container.innerHTML = '
No employees found
'; return; } // Filter employees based on search query const filteredEmployees = this.searchQuery ? this.employees.filter(emp => emp.name.toLowerCase().includes(this.searchQuery.toLowerCase()) || emp.mac.toLowerCase().includes(this.searchQuery.toLowerCase()) ) : this.employees; if (filteredEmployees.length === 0) { container.innerHTML = '
No employees match your search
'; return; } container.innerHTML = filteredEmployees.map(employee => `
${employee.picture ? `${employee.name}` : employee.name.charAt(0).toUpperCase() }
${employee.name}
${employee.mac}
${employee.status}
${employee.time_in !== 'N/A' ? `In: ${employee.time_in}` : 'Not checked in'}
`).join(''); } renderEvents() { const container = document.getElementById('eventsList'); if (this.events.length === 0) { container.innerHTML = '
No recent events
'; return; } container.innerHTML = this.events.map(event => `
${event.employee_name}
${this.formatEventType(event.event_type)}
${event.time_ago}
`).join(''); } renderDailySummary() { const container = document.getElementById('summaryTable'); if (this.summary.length === 0) { container.innerHTML = '
No summary data for selected date
'; return; } container.innerHTML = ` ${this.summary.map(emp => ` `).join('')}
Employee Time In Time Out Work Duration Break Duration Status
${emp.name.charAt(0).toUpperCase()}
${emp.name}
${emp.mac_address}
${emp.time_in || 'N/A'} ${emp.time_out || 'N/A'} ${emp.total_work_formatted} ${emp.total_break_formatted} ${emp.status}
`; } getEventIcon(eventType) { const icons = { 'time_in': 'fa-sign-in-alt', 'time_out': 'fa-sign-out-alt', 'break_start': 'fa-coffee', 'break_end': 'fa-play', 'timeout_5pm': 'fa-clock' }; return icons[eventType] || 'fa-circle'; } formatEventType(eventType) { const formats = { 'time_in': 'Time In', 'time_out': 'Time Out', 'break_start': 'Break Start', 'break_end': 'Break End', 'timeout_5pm': '5 PM Timeout' }; return formats[eventType] || eventType; } async startMonitoring() { try { const response = await fetch('/api/start_monitoring', { method: 'POST' }); const result = await response.json(); if (result.success) { this.isMonitoring = true; this.updateControlButtons(); this.showNotification('Monitoring started successfully', 'success'); await this.loadSystemStatus(); } else { this.showNotification(result.message, 'error'); } } catch (error) { console.error('Error starting monitoring:', error); this.showNotification('Error starting monitoring', 'error'); } } async stopMonitoring() { try { const response = await fetch('/api/stop_monitoring', { method: 'POST' }); const result = await response.json(); if (result.success) { this.isMonitoring = false; this.updateControlButtons(); this.showNotification('Monitoring stopped', 'warning'); await this.loadSystemStatus(); } else { this.showNotification(result.message, 'error'); } } catch (error) { console.error('Error stopping monitoring:', error); this.showNotification('Error stopping monitoring', 'error'); } } async refreshData() { document.getElementById('lastUpdate').textContent = new Date().toLocaleTimeString(); await this.loadInitialData(); this.showNotification('Data refreshed', 'success'); } async exportCSV() { try { const date = document.getElementById('summaryDate').value; const response = await fetch(`/api/export_csv?date=${date}`); const result = await response.json(); if (result.success) { this.showNotification('CSV exported successfully', 'success'); } else { this.showNotification(result.message, 'error'); } } catch (error) { console.error('Error exporting CSV:', error); this.showNotification('Error exporting CSV', 'error'); } } performSearch() { this.searchQuery = document.getElementById('searchInput').value.trim(); this.renderEmployees(); if (this.searchQuery) { this.showNotification(`Searching for: ${this.searchQuery}`, 'info'); } } clearSearch() { this.searchQuery = ''; document.getElementById('searchInput').value = ''; this.renderEmployees(); } updateControlButtons() { const startBtn = document.getElementById('startBtn'); const stopBtn = document.getElementById('stopBtn'); startBtn.disabled = this.isMonitoring; stopBtn.disabled = !this.isMonitoring; } startAutoUpdate() { // Update data every 10 seconds this.updateInterval = setInterval(() => { if (this.isMonitoring) { this.loadEmployees(); this.loadEvents(); this.loadSummaryStats(); } }, 10000); } updateCurrentTime() { const updateTime = () => { const now = new Date(); document.getElementById('currentTime').textContent = now.toLocaleTimeString(); }; updateTime(); setInterval(updateTime, 1000); } openModal(modalId) { const modal = document.getElementById(modalId); modal.classList.add('show'); // Load settings data if opening settings modal if (modalId === 'settingsModal') { this.loadSystemStatus(); } } closeModal(modalId) { const modal = document.getElementById(modalId); modal.classList.remove('show'); // Reset forms const forms = modal.querySelectorAll('form'); forms.forEach(form => form.reset()); } async handleAddEmployee(e) { e.preventDefault(); const formData = new FormData(e.target); const employeeData = { name: formData.get('name'), mac: formData.get('mac').toLowerCase(), picture: formData.get('picture'), password: formData.get('password') }; try { const response = await fetch('/api/add_employee', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(employeeData) }); const result = await response.json(); if (result.success) { this.showNotification('Employee added successfully', 'success'); this.closeModal('addEmployeeModal'); await this.loadEmployees(); await this.loadSystemStatus(); } else { this.showNotification(result.message, 'error'); } } catch (error) { console.error('Error adding employee:', error); this.showNotification('Error adding employee', 'error'); } } async handleChangePassword(e) { e.preventDefault(); const formData = new FormData(e.target); const newPassword = formData.get('newPassword'); const confirmPassword = formData.get('confirmPassword'); if (newPassword !== confirmPassword) { this.showNotification('Passwords do not match', 'error'); return; } const passwordData = { currentPassword: formData.get('currentPassword'), newPassword: newPassword }; try { const response = await fetch('/api/change_password', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(passwordData) }); const result = await response.json(); if (result.success) { this.showNotification('Password changed successfully', 'success'); this.closeModal('settingsModal'); } else { this.showNotification(result.message, 'error'); } } catch (error) { console.error('Error changing password:', error); this.showNotification('Error changing password', 'error'); } } showEmployeeDetails(mac) { const employee = this.employees.find(emp => emp.mac === mac); if (!employee) return; const modal = document.getElementById('employeeDetailsModal'); const content = document.getElementById('employeeDetailsContent'); content.innerHTML = `
${employee.picture ? `${employee.name}` : employee.name.charAt(0).toUpperCase() }

${employee.name}

${employee.mac}

Current Status: ${employee.status}
Time In: ${employee.time_in}
Last Seen: ${employee.last_seen}
Is Present: ${employee.is_present ? 'Yes' : 'No'}
`; this.openModal('employeeDetailsModal'); // Store current employee for delete/modify operations this.currentEmployee = employee; // Add event listeners for delete and modify buttons document.getElementById('deleteEmployeeBtn').onclick = () => this.openDeleteEmployeeModal(); document.getElementById('modifyEmployeeBtn').onclick = () => this.openModifyEmployeeModal(); } openDeleteEmployeeModal() { if (!this.currentEmployee) return; // Populate delete employee info document.getElementById('deleteEmployeeInfo').innerHTML = `

${this.currentEmployee.name}

${this.currentEmployee.mac}

Status: ${this.currentEmployee.status}

`; // Clear password field document.getElementById('deleteAdminPassword').value = ''; this.closeModal('employeeDetailsModal'); this.openModal('deleteEmployeeModal'); } openModifyEmployeeModal() { if (!this.currentEmployee) return; // Pre-fill the form with current employee data document.getElementById('modifyEmployeeName').value = this.currentEmployee.name; document.getElementById('modifyEmployeeMac').value = this.currentEmployee.mac; document.getElementById('modifyEmployeePicture').value = this.currentEmployee.picture || ''; document.getElementById('modifyAdminPassword').value = ''; this.closeModal('employeeDetailsModal'); this.openModal('modifyEmployeeModal'); } async handleDeleteEmployee(e) { e.preventDefault(); if (!this.currentEmployee) { this.showNotification('No employee selected', 'error'); return; } const formData = new FormData(e.target); const password = formData.get('password'); if (!password) { this.showNotification('Please enter admin password', 'error'); return; } try { const response = await fetch('/api/delete_employee', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ employee_id: this.getEmployeeIdByMac(this.currentEmployee.mac), password: password }) }); const result = await response.json(); if (result.success) { this.showNotification(result.message, 'success'); this.closeModal('deleteEmployeeModal'); await this.loadEmployees(); await this.loadSummaryStats(); } else { this.showNotification(result.message, 'error'); } } catch (error) { console.error('Error deleting employee:', error); this.showNotification('Error deleting employee', 'error'); } } async handleModifyEmployee(e) { e.preventDefault(); if (!this.currentEmployee) { this.showNotification('No employee selected', 'error'); return; } const formData = new FormData(e.target); const name = formData.get('name'); const mac_address = formData.get('mac_address'); const picture_path = formData.get('picture_path'); const password = formData.get('password'); if (!password) { this.showNotification('Please enter admin password', 'error'); return; } if (!name || !mac_address) { this.showNotification('Please fill in all required fields', 'error'); return; } try { const response = await fetch('/api/modify_employee', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ employee_id: this.getEmployeeIdByMac(this.currentEmployee.mac), name: name, mac_address: mac_address, picture_path: picture_path, password: password }) }); const result = await response.json(); if (result.success) { this.showNotification(result.message, 'success'); this.closeModal('modifyEmployeeModal'); await this.loadEmployees(); await this.loadSummaryStats(); } else { this.showNotification(result.message, 'error'); } } catch (error) { console.error('Error modifying employee:', error); this.showNotification('Error modifying employee', 'error'); } } getEmployeeIdByMac(mac) { // Since we don't have employee ID in the frontend data, we'll need to get it from the API // For now, we'll use the MAC address as identifier and let the backend handle the ID lookup return mac; } showNotification(message, type = 'info') { const container = document.getElementById('notifications'); const notification = document.createElement('div'); notification.className = `notification ${type}`; const title = type.charAt(0).toUpperCase() + type.slice(1); notification.innerHTML = `
${title}
${message}
`; container.appendChild(notification); // Auto remove after 5 seconds setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 5000); } } // Global functions for modal management function closeModal(modalId) { if (window.attendanceTracker) { window.attendanceTracker.closeModal(modalId); } } // Initialize the application when DOM is loaded document.addEventListener('DOMContentLoaded', () => { window.attendanceTracker = new AttendanceTracker(); });