const POCKETBASE_URL = 'https://pocketbase.ccllc.pro'; const pb = new PocketBase(POCKETBASE_URL); // Load auth from localStorage or cookies try { pb.authStore.loadFromCookie(document.cookie); } catch (e) { const storedToken = localStorage.getItem('pb_auth_token'); const storedModel = localStorage.getItem('pb_auth_model'); if (storedToken && storedModel) { try { pb.authStore.save(storedToken, JSON.parse(storedModel)); } catch (e2) { console.error('Error loading auth:', e2); } } } let currentPage = 1; const perPage = 50; let allEmployees = []; let currentFilter = 'all'; let currentSearch = ''; let currentEditingEmployeeId = null; let quickReportEditor = null; // Get auth token for API requests function getAuthToken() { // Try to get from PocketBase auth store first if (pb.authStore.token) { return pb.authStore.token; } // Fallback to localStorage const storedToken = localStorage.getItem('pb_auth_token'); if (storedToken) { // Also update the auth store with the token const storedModel = localStorage.getItem('pb_auth_model'); if (storedModel) { try { pb.authStore.save(storedToken, JSON.parse(storedModel)); } catch (e) { console.error('Error loading auth from localStorage:', e); } } return storedToken; } return ''; } // Show message function showMessage(message, type = 'success') { const container = document.getElementById('messageContainer'); const bgColor = type === 'success' ? 'bg-green-50 border-green-200 text-green-700' : 'bg-red-50 border-red-200 text-red-700'; container.innerHTML = `
${message}
`; setTimeout(() => { container.innerHTML = ''; }, 5000); } // Load employees async function loadEmployees(page = 1) { try { const token = getAuthToken(); console.log('Loading employees, token present:', !!token); if (!token) { console.error('No auth token found'); window.location.href = '/index.html'; return; } const url = `/api/employees?page=${page}&perPage=${perPage}&sort=Last_Name`; console.log('Fetching from:', url); const response = await fetch(url, { headers: { 'Authorization': `Bearer ${token}`, }, }); console.log('Response status:', response.status); if (response.status === 401) { console.error('Unauthorized - redirecting to login'); window.location.href = '/index.html'; return; } if (!response.ok) { const error = await response.json(); console.error('Error response:', error); throw new Error(error.error || 'Failed to load employees'); } const data = await response.json(); console.log('Successfully loaded employees:', data); allEmployees = data.items; applyFiltersAndSearch(); displayPagination(data); currentPage = page; } catch (error) { console.error('Error loading employees:', error); showMessage(error.message || 'Failed to load employees', 'error'); document.getElementById('employeesTableBody').innerHTML = ` Error loading employees: ${error.message} `; } } // Filter and search employees function applyFiltersAndSearch() { let filtered = [...allEmployees]; // Apply type filter if (currentFilter === 'employee') { filtered = filtered.filter(emp => emp.Type === 'Employee'); } else if (currentFilter === 'sub-contractor') { filtered = filtered.filter(emp => emp.Type === 'Sub-Contractor'); } // Apply search if (currentSearch.trim()) { const searchLower = currentSearch.toLowerCase().trim(); filtered = filtered.filter(emp => { const firstName = (emp.First_Name || '').toLowerCase(); const lastName = (emp.Last_Name || '').toLowerCase(); const id = (emp.id || '').toLowerCase(); const fullName = `${firstName} ${lastName}`; return firstName.includes(searchLower) || lastName.includes(searchLower) || fullName.includes(searchLower) || id.includes(searchLower); }); } // Sort by last name A to Z filtered.sort((a, b) => { const lastNameA = (a.Last_Name || '').toLowerCase(); const lastNameB = (b.Last_Name || '').toLowerCase(); if (lastNameA < lastNameB) return -1; if (lastNameA > lastNameB) return 1; // If last names are the same, sort by first name const firstNameA = (a.First_Name || '').toLowerCase(); const firstNameB = (b.First_Name || '').toLowerCase(); if (firstNameA < firstNameB) return -1; if (firstNameA > firstNameB) return 1; return 0; }); displayEmployees(filtered); } // Display employees in table function displayEmployees(employees) { const tbody = document.getElementById('employeesTableBody'); if (employees.length === 0) { tbody.innerHTML = ` No employees found `; return; } tbody.innerHTML = employees.map(emp => ` ${emp.id || '-'} ${emp.First_Name || '-'} ${emp.Last_Name || '-'} ${emp.Type || '-'} `).join(''); } // Update filter button styles function updateFilterButtons() { const allBtn = document.getElementById('filterAll'); const employeeBtn = document.getElementById('filterEmployee'); const subContractorBtn = document.getElementById('filterSubContractor'); // Reset all buttons [allBtn, employeeBtn, subContractorBtn].forEach(btn => { btn.classList.remove('bg-indigo-600', 'text-white', 'hover:bg-indigo-700'); btn.classList.add('bg-gray-200', 'text-gray-700', 'hover:bg-gray-300'); }); // Set active button let activeBtn; if (currentFilter === 'all') { activeBtn = allBtn; } else if (currentFilter === 'employee') { activeBtn = employeeBtn; } else if (currentFilter === 'sub-contractor') { activeBtn = subContractorBtn; } if (activeBtn) { activeBtn.classList.remove('bg-gray-200', 'text-gray-700', 'hover:bg-gray-300'); activeBtn.classList.add('bg-indigo-600', 'text-white', 'hover:bg-indigo-700'); } } // Display pagination function displayPagination(data) { const pagination = document.getElementById('pagination'); if (data.totalPages <= 1) { pagination.innerHTML = `
Total: ${data.totalItems} employees
`; return; } pagination.innerHTML = `
Showing ${(data.page - 1) * data.perPage + 1} to ${Math.min(data.page * data.perPage, data.totalItems)} of ${data.totalItems} employees
Page ${data.page} of ${data.totalPages}
`; } // Open modal for adding employee function openAddModal() { currentEditingEmployeeId = null; document.getElementById('modalTitle').textContent = 'Add Employee'; document.getElementById('employeeForm').reset(); document.getElementById('employeeId').value = ''; // Hide Reports tab for new employees document.getElementById('employeeReportsTab').style.display = 'none'; // Show Employee Info tab switchToEmployeeInfoTab(); document.getElementById('employeeModal').classList.remove('hidden'); } // Switch to Employee Info tab function switchToEmployeeInfoTab() { document.getElementById('employeeInfoTab').classList.add('border-indigo-500', 'text-indigo-600'); document.getElementById('employeeInfoTab').classList.remove('border-transparent', 'text-gray-500'); document.getElementById('employeeReportsTab').classList.remove('border-indigo-500', 'text-indigo-600'); document.getElementById('employeeReportsTab').classList.add('border-transparent', 'text-gray-500'); document.getElementById('employeeInfoContent').classList.remove('hidden'); document.getElementById('employeeReportsContent').classList.add('hidden'); } // Switch to Reports tab function switchToReportsTab() { document.getElementById('employeeReportsTab').classList.add('border-indigo-500', 'text-indigo-600'); document.getElementById('employeeReportsTab').classList.remove('border-transparent', 'text-gray-500'); document.getElementById('employeeInfoTab').classList.remove('border-indigo-500', 'text-indigo-600'); document.getElementById('employeeInfoTab').classList.add('border-transparent', 'text-gray-500'); document.getElementById('employeeInfoContent').classList.add('hidden'); document.getElementById('employeeReportsContent').classList.remove('hidden'); } // Initialize quick report editor function initQuickReportEditor() { if (!quickReportEditor) { const editorContainer = document.getElementById('quickReportEditor'); if (editorContainer && typeof Quill !== 'undefined') { try { quickReportEditor = new Quill('#quickReportEditor', { theme: 'snow', modules: { toolbar: [ [{ 'header': [1, 2, 3, false] }], ['bold', 'italic', 'underline'], [{ 'list': 'ordered'}, { 'list': 'bullet' }], ['link'], ['clean'] ] } }); // Force text color to be dark const style = document.createElement('style'); style.textContent = ` #quickReportEditor .ql-editor { color: #1f2937 !important; background-color: #ffffff !important; } #quickReportEditor .ql-editor.ql-blank::before { color: #9ca3af !important; } #quickReportEditor .ql-editor p, #quickReportEditor .ql-editor h1, #quickReportEditor .ql-editor h2, #quickReportEditor .ql-editor h3 { color: #1f2937 !important; } `; document.head.appendChild(style); quickReportEditor.on('text-change', function() { const htmlContent = quickReportEditor.root.innerHTML; document.getElementById('quickReport').value = htmlContent; }); } catch (error) { console.error('Error initializing quick report editor:', error); } } } return quickReportEditor; } // Load employee reports async function loadEmployeeReports(employeeId) { try { const token = getAuthToken(); const response = await fetch(`/api/reports?employee=${employeeId}&perPage=1000&sort=-Report_Date`, { headers: { 'Authorization': `Bearer ${token}`, }, }); if (!response.ok) { throw new Error('Failed to load reports'); } const data = await response.json(); displayEmployeeReports(data.items || []); } catch (error) { console.error('Error loading employee reports:', error); document.getElementById('employeeReportsList').innerHTML = '
Failed to load reports
'; } } // Display employee reports in post board style function displayEmployeeReports(reports) { const container = document.getElementById('employeeReportsList'); if (reports.length === 0) { container.innerHTML = '
No reports yet. Add one to get started!
'; return; } const reportsHtml = reports.map(report => { // Format date and time (convert from UTC to local for display) const reportDate = report.Report_Date ? new Date(report.Report_Date).toLocaleString(undefined, { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', timeZoneName: 'short' }) : 'No date'; const sentimentColor = { 'Positive': 'bg-green-100 text-green-800', 'Negative': 'bg-red-100 text-red-800', 'Neutral': 'bg-gray-100 text-gray-800' }[report.Centiment] || 'bg-gray-100 text-gray-800'; const reportText = report.Report || ''; // Collect all links and detect Voxer links const links = []; const isVoxerLink = (url) => { if (!url) return false; const lowerUrl = url.toLowerCase(); return lowerUrl.includes('voxer.com') || lowerUrl.includes('voxer'); }; if (report.Link_01) links.push({ num: 1, url: report.Link_01, isVoxer: isVoxerLink(report.Link_01) }); if (report.Link_02) links.push({ num: 2, url: report.Link_02, isVoxer: isVoxerLink(report.Link_02) }); if (report.Link_03) links.push({ num: 3, url: report.Link_03, isVoxer: isVoxerLink(report.Link_03) }); if (report.Link_04) links.push({ num: 4, url: report.Link_04, isVoxer: isVoxerLink(report.Link_04) }); if (report.Link_05) links.push({ num: 5, url: report.Link_05, isVoxer: isVoxerLink(report.Link_05) }); return `
📅 ${reportDate} ${report.Centiment ? `${escapeHtml(report.Centiment)}` : ''}
${report.Report || 'No content'}
${links.length > 0 ? `
Links:
${links.map(link => { if (link.isVoxer) { return ` 🎙️ Voxer ${link.num} `; } else { return ` 🔗 Link ${link.num} `; } }).join('')}
` : ''}
`; }).join(''); container.innerHTML = reportsHtml; } // Edit employee report let currentEditingReportId = null; window.editEmployeeReport = async function(reportId) { try { currentEditingReportId = reportId; const token = getAuthToken(); const response = await fetch(`/api/reports/${reportId}`, { headers: { 'Authorization': `Bearer ${token}`, }, }); if (!response.ok) { throw new Error('Failed to load report'); } const report = await response.json(); // Populate the quick report form initQuickReportEditor(); // Set rich text editor content const reportContent = report.Report || ''; setTimeout(() => { if (quickReportEditor) { quickReportEditor.root.innerHTML = reportContent || '


'; document.getElementById('quickReport').value = reportContent; } }, 100); document.getElementById('quickSentiment').value = report.Centiment || ''; // Format date and time for input field (convert from UTC to local) if (report.Report_Date) { const date = new Date(report.Report_Date); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); document.getElementById('quickReportDate').value = `${year}-${month}-${day}T${hours}:${minutes}`; } else { document.getElementById('quickReportDate').value = ''; } // Set links document.getElementById('quickLink01').value = report.Link_01 || ''; document.getElementById('quickLink02').value = report.Link_02 || ''; document.getElementById('quickLink03').value = report.Link_03 || ''; document.getElementById('quickLink04').value = report.Link_04 || ''; document.getElementById('quickLink05').value = report.Link_05 || ''; // Update form title const formTitle = document.querySelector('#addReportForm h4'); if (formTitle) { formTitle.textContent = 'Edit Report'; } // Show the form document.getElementById('addReportForm').classList.remove('hidden'); // Scroll to form document.getElementById('addReportForm').scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } catch (error) { console.error('Error loading report:', error); showMessage('Failed to load report', 'error'); } }; // Delete employee report window.deleteEmployeeReport = async function(reportId) { if (!confirm('Are you sure you want to delete this report?')) { return; } try { const token = getAuthToken(); const response = await fetch(`/api/reports/${reportId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}`, }, }); if (!response.ok) { throw new Error('Failed to delete report'); } // Reload reports if (currentEditingEmployeeId) { await loadEmployeeReports(currentEditingEmployeeId); } showMessage('Report deleted successfully', 'success'); } catch (error) { console.error('Error deleting report:', error); showMessage('Failed to delete report', 'error'); } }; // Edit employee async function editEmployee(id) { try { currentEditingEmployeeId = id; const token = getAuthToken(); const response = await fetch(`/api/employees/${id}`, { headers: { 'Authorization': `Bearer ${token}`, }, }); if (!response.ok) { throw new Error('Failed to load employee'); } const employee = await response.json(); document.getElementById('modalTitle').textContent = 'Edit Employee'; document.getElementById('employeeId').value = employee.id || ''; document.getElementById('firstName').value = employee.First_Name || ''; document.getElementById('lastName').value = employee.Last_Name || ''; document.getElementById('email').value = employee.Email || ''; document.getElementById('phoneNumber').value = employee.Phone_Number || ''; document.getElementById('voxerId').value = employee.Voxer_ID || ''; document.getElementById('type').value = employee.Type || ''; // Show Reports tab button document.getElementById('employeeReportsTab').style.display = 'block'; // Show Employee Info tab by default switchToEmployeeInfoTab(); // Load employee reports await loadEmployeeReports(id); document.getElementById('employeeModal').classList.remove('hidden'); } catch (error) { console.error('Error loading employee:', error); showMessage('Failed to load employee', 'error'); } } // Delete employee async function deleteEmployee(id) { if (!confirm('Are you sure you want to delete this employee?')) { return; } try { const token = getAuthToken(); const response = await fetch(`/api/employees/${id}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}`, }, }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to delete employee'); } showMessage('Employee deleted successfully'); // Reset search and filter when reloading currentSearch = ''; currentFilter = 'all'; const searchInput = document.getElementById('searchInput'); if (searchInput) { searchInput.value = ''; } updateFilterButtons(); loadEmployees(currentPage); } catch (error) { console.error('Error deleting employee:', error); showMessage(error.message || 'Failed to delete employee', 'error'); } } // Initialize page - check auth and display user info async function initPage() { // First, ensure we have the token loaded from localStorage if not in auth store const token = getAuthToken(); if (!token) { // No token at all, redirect to login console.log('No auth token found, redirecting to login'); window.location.href = '/index.html'; return; } // Check if auth store is valid, if not try to refresh if (!pb.authStore.isValid) { try { // Try to refresh the token await pb.collection('users').authRefresh(); // Update localStorage after refresh if (pb.authStore.token && pb.authStore.model) { localStorage.setItem('pb_auth_token', pb.authStore.token); localStorage.setItem('pb_auth_model', JSON.stringify(pb.authStore.model)); } } catch (e) { console.error('Token refresh failed:', e); // If refresh fails but we have a token, still try to continue // The API will validate it if (!pb.authStore.token) { pb.authStore.clear(); localStorage.removeItem('pb_auth_token'); localStorage.removeItem('pb_auth_model'); window.location.href = '/index.html'; return; } } } // Display user information const user = pb.authStore.model; if (user) { const userEmailElement = document.getElementById('userEmail'); if (userEmailElement) { userEmailElement.textContent = user.email || 'User'; } } else { // Try to get user from localStorage const storedModel = localStorage.getItem('pb_auth_model'); if (storedModel) { try { const userData = JSON.parse(storedModel); const userEmailElement = document.getElementById('userEmail'); if (userEmailElement) { userEmailElement.textContent = userData.email || 'User'; } } catch (e) { console.error('Error parsing stored user model:', e); } } } // Handle logout button const logoutButton = document.getElementById('logoutButton'); if (logoutButton) { logoutButton.addEventListener('click', async () => { try { await pb.authStore.clear(); localStorage.removeItem('pb_auth_token'); localStorage.removeItem('pb_auth_model'); window.location.href = '/index.html'; } catch (error) { console.error('Logout error:', error); pb.authStore.clear(); localStorage.removeItem('pb_auth_token'); localStorage.removeItem('pb_auth_model'); window.location.href = '/index.html'; } }); } } // Handle form submission document.addEventListener('DOMContentLoaded', () => { // Initialize page (auth check, user display, logout) initPage(); // Load employees on page load loadEmployees(); // Add employee button document.getElementById('addEmployeeBtn').addEventListener('click', openAddModal); // Cancel button document.getElementById('cancelBtn').addEventListener('click', () => { document.getElementById('employeeModal').classList.add('hidden'); document.getElementById('addReportForm').classList.add('hidden'); if (quickReportEditor) { quickReportEditor.setContents([]); quickReportEditor.root.innerHTML = '


'; } }); // Close modal button const closeModalBtn = document.getElementById('closeEmployeeModal'); if (closeModalBtn) { closeModalBtn.addEventListener('click', () => { document.getElementById('employeeModal').classList.add('hidden'); document.getElementById('addReportForm').classList.add('hidden'); if (quickReportEditor) { quickReportEditor.setContents([]); quickReportEditor.root.innerHTML = '


'; } }); } // Tab switching document.getElementById('employeeInfoTab').addEventListener('click', switchToEmployeeInfoTab); document.getElementById('employeeReportsTab').addEventListener('click', switchToReportsTab); // Add Report button const addReportBtn = document.getElementById('addReportBtn'); if (addReportBtn) { addReportBtn.addEventListener('click', () => { currentEditingReportId = null; initQuickReportEditor(); document.getElementById('addReportForm').classList.remove('hidden'); // Set current date/time as default const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); document.getElementById('quickReportDate').value = `${year}-${month}-${day}T${hours}:${minutes}`; // Reset form title const formTitle = document.querySelector('#addReportForm h4'); if (formTitle) { formTitle.textContent = 'Add New Report'; } }); } // Cancel quick report const cancelQuickReport = document.getElementById('cancelQuickReport'); if (cancelQuickReport) { cancelQuickReport.addEventListener('click', () => { document.getElementById('addReportForm').classList.add('hidden'); currentEditingReportId = null; if (quickReportEditor) { quickReportEditor.setContents([]); quickReportEditor.root.innerHTML = '


'; } document.getElementById('quickReportForm').reset(); // Clear all link fields document.getElementById('quickLink01').value = ''; document.getElementById('quickLink02').value = ''; document.getElementById('quickLink03').value = ''; document.getElementById('quickLink04').value = ''; document.getElementById('quickLink05').value = ''; // Reset form title const formTitle = document.querySelector('#addReportForm h4'); if (formTitle) { formTitle.textContent = 'Add New Report'; } }); } // Quick report form submission const quickReportForm = document.getElementById('quickReportForm'); if (quickReportForm) { quickReportForm.addEventListener('submit', async (e) => { e.preventDefault(); if (!currentEditingEmployeeId) { showMessage('Please save the employee first', 'error'); return; } try { const token = getAuthToken(); // Convert datetime-local to UTC ISO string let reportDateUTC = undefined; const reportDateValue = document.getElementById('quickReportDate').value; if (reportDateValue) { const localDate = new Date(reportDateValue); reportDateUTC = localDate.toISOString(); } const formData = { Employee: currentEditingEmployeeId, Report: document.getElementById('quickReport').value || undefined, Centiment: document.getElementById('quickSentiment').value || undefined, Report_Date: reportDateUTC, Link_01: document.getElementById('quickLink01').value || undefined, Link_02: document.getElementById('quickLink02').value || undefined, Link_03: document.getElementById('quickLink03').value || undefined, Link_04: document.getElementById('quickLink04').value || undefined, Link_05: document.getElementById('quickLink05').value || undefined, }; const url = currentEditingReportId ? `/api/reports/${currentEditingReportId}` : '/api/reports'; const method = currentEditingReportId ? 'PATCH' : 'POST'; const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify(formData), }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to save report'); } showMessage(`Report ${currentEditingReportId ? 'updated' : 'added'} successfully`, 'success'); document.getElementById('addReportForm').classList.add('hidden'); quickReportForm.reset(); currentEditingReportId = null; if (quickReportEditor) { quickReportEditor.setContents([]); quickReportEditor.root.innerHTML = '


'; } // Clear all link fields document.getElementById('quickLink01').value = ''; document.getElementById('quickLink02').value = ''; document.getElementById('quickLink03').value = ''; document.getElementById('quickLink04').value = ''; document.getElementById('quickLink05').value = ''; // Update form title back to "Add New Report" const formTitle = document.querySelector('#addReportForm h4'); if (formTitle) { formTitle.textContent = 'Add New Report'; } // Reload reports await loadEmployeeReports(currentEditingEmployeeId); } catch (error) { console.error('Error saving report:', error); showMessage(error.message || 'Failed to save report', 'error'); } }); } // Search input const searchInput = document.getElementById('searchInput'); if (searchInput) { searchInput.addEventListener('input', (e) => { currentSearch = e.target.value; applyFiltersAndSearch(); }); } // Filter buttons const filterAll = document.getElementById('filterAll'); const filterEmployee = document.getElementById('filterEmployee'); const filterSubContractor = document.getElementById('filterSubContractor'); if (filterAll) { filterAll.addEventListener('click', () => { currentFilter = 'all'; updateFilterButtons(); applyFiltersAndSearch(); }); } if (filterEmployee) { filterEmployee.addEventListener('click', () => { currentFilter = 'employee'; updateFilterButtons(); applyFiltersAndSearch(); }); } if (filterSubContractor) { filterSubContractor.addEventListener('click', () => { currentFilter = 'sub-contractor'; updateFilterButtons(); applyFiltersAndSearch(); }); } // Initialize filter button styles updateFilterButtons(); // Form submission document.getElementById('employeeForm').addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(e.target); const employeeId = formData.get('id'); const data = { First_Name: formData.get('First_Name') || '', Last_Name: formData.get('Last_Name') || '', Email: formData.get('Email') || '', Phone_Number: formData.get('Phone_Number') || '', Voxer_ID: formData.get('Voxer_ID') || '', Type: formData.get('Type') || '', }; // Note: ID is only needed in URL for updates, not in body // For creates, ID is optional and auto-generated if not provided try { const token = getAuthToken(); const url = employeeId ? `/api/employees/${employeeId}` : '/api/employees'; const method = employeeId ? 'PATCH' : 'POST'; const response = await fetch(url, { method: method, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify(data), }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to save employee'); } showMessage(`Employee ${employeeId ? 'updated' : 'created'} successfully`); document.getElementById('employeeModal').classList.add('hidden'); // Reset search and filter when reloading currentSearch = ''; currentFilter = 'all'; const searchInput = document.getElementById('searchInput'); if (searchInput) { searchInput.value = ''; } updateFilterButtons(); loadEmployees(currentPage); } catch (error) { console.error('Error saving employee:', error); showMessage(error.message || 'Failed to save employee', 'error'); } }); }); // Utility function to escape HTML function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Make functions globally available window.editEmployee = editEmployee; window.deleteEmployee = deleteEmployee; window.loadEmployees = loadEmployees;