Download static/js/script.js from malik-AI/WiFi_Attendence_System: direct link, hf CLI and curl.
- Browser
- Download file 27.6 kB
-
https://huggingface.co/malik-AI/WiFi_Attendence_System/resolve/refs%2Fpr%2F1/static/js/script.js
- Command line
-
hf download hf://malik-AI/WiFi_Attendence_System@refs/pr/1/static/js/script.js
-
curl -L -o script.js https://huggingface.co/malik-AI/WiFi_Attendence_System/resolve/refs%2Fpr%2F1/static/js/script.js
27.6 kB
| // 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 = '<div class="loading">No employees found</div>'; | |
| 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 = '<div class="loading">No employees match your search</div>'; | |
| return; | |
| } | |
| container.innerHTML = filteredEmployees.map(employee => ` | |
| <div class="employee-card" onclick="attendanceTracker.showEmployeeDetails('${employee.mac}')"> | |
| <div class="employee-avatar"> | |
| ${employee.picture ? | |
| `<img src="${employee.picture}" alt="${employee.name}" onerror="this.style.display='none'; this.parentNode.textContent='${employee.name.charAt(0).toUpperCase()}'">` : | |
| employee.name.charAt(0).toUpperCase() | |
| } | |
| </div> | |
| <div class="employee-info"> | |
| <div class="employee-name">${employee.name}</div> | |
| <div class="employee-mac">${employee.mac}</div> | |
| </div> | |
| <div class="employee-status"> | |
| <span class="status-badge ${employee.status.toLowerCase().replace(' ', '')}">${employee.status}</span> | |
| <div class="employee-time"> | |
| ${employee.time_in !== 'N/A' ? `In: ${employee.time_in}` : 'Not checked in'} | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| renderEvents() { | |
| const container = document.getElementById('eventsList'); | |
| if (this.events.length === 0) { | |
| container.innerHTML = '<div class="loading">No recent events</div>'; | |
| return; | |
| } | |
| container.innerHTML = this.events.map(event => ` | |
| <div class="event-item ${event.event_type}"> | |
| <div class="event-icon"> | |
| <i class="fas ${this.getEventIcon(event.event_type)}"></i> | |
| </div> | |
| <div class="event-content"> | |
| <div class="event-name">${event.employee_name}</div> | |
| <div class="event-type">${this.formatEventType(event.event_type)}</div> | |
| </div> | |
| <div class="event-time">${event.time_ago}</div> | |
| </div> | |
| `).join(''); | |
| } | |
| renderDailySummary() { | |
| const container = document.getElementById('summaryTable'); | |
| if (this.summary.length === 0) { | |
| container.innerHTML = '<div class="loading">No summary data for selected date</div>'; | |
| return; | |
| } | |
| container.innerHTML = ` | |
| <table> | |
| <thead> | |
| <tr> | |
| <th>Employee</th> | |
| <th>Time In</th> | |
| <th>Time Out</th> | |
| <th>Work Duration</th> | |
| <th>Break Duration</th> | |
| <th>Status</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| ${this.summary.map(emp => ` | |
| <tr> | |
| <td> | |
| <div style="display: flex; align-items: center; gap: 8px;"> | |
| <div class="employee-avatar" style="width: 32px; height: 32px; font-size: 12px;"> | |
| ${emp.name.charAt(0).toUpperCase()} | |
| </div> | |
| <div> | |
| <div style="font-weight: 600;">${emp.name}</div> | |
| <div style="font-size: 11px; color: var(--text-muted);">${emp.mac_address}</div> | |
| </div> | |
| </div> | |
| </td> | |
| <td>${emp.time_in || 'N/A'}</td> | |
| <td>${emp.time_out || 'N/A'}</td> | |
| <td>${emp.total_work_formatted}</td> | |
| <td>${emp.total_break_formatted}</td> | |
| <td><span class="status-badge ${emp.status.toLowerCase().replace(' ', '')}">${emp.status}</span></td> | |
| </tr> | |
| `).join('')} | |
| </tbody> | |
| </table> | |
| `; | |
| } | |
| 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 = ` | |
| <div style="text-align: center; margin-bottom: 20px;"> | |
| <div class="employee-avatar" style="width: 80px; height: 80px; font-size: 32px; margin: 0 auto 12px;"> | |
| ${employee.picture ? | |
| `<img src="${employee.picture}" alt="${employee.name}" style="width: 100%; height: 100%; object-fit: cover;">` : | |
| employee.name.charAt(0).toUpperCase() | |
| } | |
| </div> | |
| <h3>${employee.name}</h3> | |
| <p style="color: var(--text-muted); font-family: monospace;">${employee.mac}</p> | |
| </div> | |
| <div class="info-grid"> | |
| <div class="info-item"> | |
| <span class="info-label">Current Status:</span> | |
| <span class="status-badge ${employee.status.toLowerCase().replace(' ', '')}">${employee.status}</span> | |
| </div> | |
| <div class="info-item"> | |
| <span class="info-label">Time In:</span> | |
| <span class="info-value">${employee.time_in}</span> | |
| </div> | |
| <div class="info-item"> | |
| <span class="info-label">Last Seen:</span> | |
| <span class="info-value">${employee.last_seen}</span> | |
| </div> | |
| <div class="info-item"> | |
| <span class="info-label">Is Present:</span> | |
| <span class="info-value">${employee.is_present ? 'Yes' : 'No'}</span> | |
| </div> | |
| </div> | |
| `; | |
| 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 = ` | |
| <div style="text-align: center; padding: 15px; background: #fee; border: 1px solid #fcc; border-radius: 8px;"> | |
| <h4>${this.currentEmployee.name}</h4> | |
| <p style="font-family: monospace; color: #666;">${this.currentEmployee.mac}</p> | |
| <p style="color: #999;">Status: ${this.currentEmployee.status}</p> | |
| </div> | |
| `; | |
| // 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 = ` | |
| <div class="notification-title">${title}</div> | |
| <div class="notification-message">${message}</div> | |
| `; | |
| 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(); | |
| }); | |