const POCKETBASE_URL = 'https://pocketbase.ccllc.pro'; const API_BASE = '/api/reports'; const EMPLOYEES_API = '/api/employees'; // Initialize PocketBase client const pb = new PocketBase(POCKETBASE_URL); // Load auth from localStorage/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); } } } // 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 ''; } // Load employees for dropdown let employeesList = []; // Rich text editor instance let reportEditor = null; async function loadEmployees() { try { const token = getAuthToken(); if (!token) { return; } const response = await fetch(`${EMPLOYEES_API}?page=1&perPage=1000`, { headers: { 'Authorization': `Bearer ${token}`, }, }); if (!response.ok) { throw new Error('Failed to load employees'); } const data = await response.json(); employeesList = data.items || []; populateEmployeeDropdown(); } catch (error) { console.error('Error loading employees:', error); } } function populateEmployeeDropdown() { const employeeSelect = document.getElementById('Employee'); if (!employeeSelect) return; // Clear existing options except the first one while (employeeSelect.options.length > 1) { employeeSelect.remove(1); } employeesList.forEach(emp => { const option = document.createElement('option'); option.value = emp.id; const name = `${emp.First_Name || ''} ${emp.Last_Name || ''}`.trim() || emp.id; option.textContent = `${name} (${emp.id})`; employeeSelect.appendChild(option); }); } // 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 and display reports let currentPage = 1; let totalPages = 1; async function loadReports(page = 1) { const loadingIndicator = document.getElementById('loadingIndicator'); const tableContainer = document.getElementById('reportsTableContainer'); const emptyState = document.getElementById('emptyState'); const tableBody = document.getElementById('reportsTableBody'); loadingIndicator.classList.remove('hidden'); tableContainer.classList.add('hidden'); emptyState.classList.add('hidden'); try { const token = getAuthToken(); if (!token) { window.location.href = '/index.html'; return; } const response = await fetch(`${API_BASE}?page=${page}&perPage=50`, { headers: { 'Authorization': `Bearer ${token}`, }, }); if (response.status === 401) { window.location.href = '/index.html'; return; } if (!response.ok) { throw new Error('Failed to load reports'); } const data = await response.json(); currentPage = data.page; totalPages = data.totalPages; loadingIndicator.classList.add('hidden'); if (data.items.length === 0) { emptyState.classList.remove('hidden'); return; } tableContainer.classList.remove('hidden'); renderReports(data.items); renderPagination(); } catch (error) { console.error('Error loading reports:', error); loadingIndicator.classList.add('hidden'); showMessage('Failed to load reports. Please try again.', 'error'); } } // Render reports table function renderReports(reports) { const tableBody = document.getElementById('reportsTableBody'); tableBody.innerHTML = ''; reports.forEach(report => { const row = document.createElement('tr'); row.className = 'hover:bg-gray-50'; // Get employee name from expanded relation let employeeName = '-'; if (report.expand && report.expand.Employee) { const emp = report.expand.Employee; employeeName = `${emp.First_Name || ''} ${emp.Last_Name || ''}`.trim() || emp.id || '-'; } else if (report.Employee) { employeeName = report.Employee; } // Format report text (strip HTML tags and truncate if too long) const reportHtml = report.Report || '-'; // Strip HTML tags for preview const reportText = reportHtml.replace(/<[^>]*>/g, '').trim(); const reportPreview = reportText.length > 100 ? reportText.substring(0, 100) + '...' : reportText; // 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' }) : '-'; // Count and display links (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) }); const linksHtml = links.length > 0 ? links.map(link => { if (link.isVoxer) { return `🎙️ Voxer ${link.num}`; } else { return `🔗 Link ${link.num}`; } }).join('') : '-'; row.innerHTML = ` ${escapeHtml(employeeName)}
${reportPreview !== '-' ? escapeHtml(reportPreview) : '-'}
${escapeHtml(report.Centiment || '-')} ${escapeHtml(reportDate)}
${linksHtml === '-' ? '-' : linksHtml}
`; tableBody.appendChild(row); }); } // Render pagination function renderPagination() { const container = document.getElementById('paginationContainer'); if (totalPages <= 1) { container.innerHTML = ''; return; } let html = '
'; html += '
'; if (currentPage > 1) { html += ``; } if (currentPage < totalPages) { html += ``; } html += '
'; html += '
'; container.innerHTML = html; } // Initialize rich text editor function initRichTextEditor() { if (!reportEditor) { const editorContainer = document.getElementById('reportEditor'); if (editorContainer && typeof Quill !== 'undefined') { try { reportEditor = new Quill('#reportEditor', { theme: 'snow', modules: { toolbar: [ [{ 'header': [1, 2, 3, false] }], ['bold', 'italic', 'underline', 'strike'], [{ 'list': 'ordered'}, { 'list': 'bullet' }], [{ 'color': [] }, { 'background': [] }], ['link'], ['clean'] ] } }); // Sync editor content with hidden textarea for form submission reportEditor.on('text-change', function() { const htmlContent = reportEditor.root.innerHTML; const textarea = document.getElementById('Report'); if (textarea) { textarea.value = htmlContent; } }); } catch (error) { console.error('Error initializing rich text editor:', error); } } } return reportEditor; } // Open modal for add/edit async function openModal(reportId = null) { const modal = document.getElementById('reportModal'); const form = document.getElementById('reportForm'); const modalTitle = document.getElementById('modalTitle'); const reportIdInput = document.getElementById('reportId'); // Show modal first so editor container is visible modal.classList.remove('hidden'); // Initialize rich text editor (with a small delay to ensure DOM is ready) setTimeout(() => { initRichTextEditor(); }, 50); // Ensure employees are loaded if (employeesList.length === 0) { await loadEmployees(); } if (reportId) { modalTitle.textContent = 'Edit Employee Report'; reportIdInput.value = reportId; await loadReportForEdit(reportId); } else { modalTitle.textContent = 'Add Employee Report'; reportIdInput.value = ''; form.reset(); // Clear the rich text editor after it's initialized setTimeout(() => { if (reportEditor) { reportEditor.setContents([]); reportEditor.root.innerHTML = '


'; document.getElementById('Report').value = ''; } }, 100); } } // Load report data for editing async function loadReportForEdit(id) { try { const token = getAuthToken(); const response = await fetch(`${API_BASE}/${id}`, { headers: { 'Authorization': `Bearer ${token}`, }, }); if (!response.ok) { throw new Error('Failed to load report'); } const report = await response.json(); document.getElementById('Employee').value = report.Employee || ''; // Set rich text editor content const reportContent = report.Report || ''; if (reportEditor) { // Set the HTML content in the editor reportEditor.root.innerHTML = reportContent || '


'; document.getElementById('Report').value = reportContent; } else { // Editor not initialized yet, try again after a short delay setTimeout(() => { if (reportEditor) { reportEditor.root.innerHTML = reportContent || '


'; document.getElementById('Report').value = reportContent; } else { document.getElementById('Report').value = reportContent; } }, 150); } document.getElementById('Centiment').value = report.Centiment || ''; // Format date and time for input field (YYYY-MM-DDTHH:mm) - convert from UTC to local if (report.Report_Date) { const date = new Date(report.Report_Date); // Convert UTC to local time for display 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('Report_Date').value = `${year}-${month}-${day}T${hours}:${minutes}`; } else { document.getElementById('Report_Date').value = ''; } document.getElementById('Link_01').value = report.Link_01 || ''; document.getElementById('Link_02').value = report.Link_02 || ''; document.getElementById('Link_03').value = report.Link_03 || ''; document.getElementById('Link_04').value = report.Link_04 || ''; document.getElementById('Link_05').value = report.Link_05 || ''; } catch (error) { console.error('Error loading report:', error); showMessage('Failed to load report data.', 'error'); } } // Edit report window.editReport = function(id) { openModal(id); }; // Delete report window.deleteReport = async function(id) { if (!confirm('Are you sure you want to delete this report?')) { return; } try { const token = getAuthToken(); const response = await fetch(`${API_BASE}/${id}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}`, }, }); if (!response.ok) { throw new Error('Failed to delete report'); } showMessage('Report deleted successfully.', 'success'); loadReports(currentPage); } catch (error) { console.error('Error deleting report:', error); showMessage('Failed to delete report.', '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', async () => { // Initialize page (auth check, user display, logout) await initPage(); const form = document.getElementById('reportForm'); const modal = document.getElementById('reportModal'); const closeModal = document.getElementById('closeModal'); const cancelButton = document.getElementById('cancelButton'); const addReportButton = document.getElementById('addReportButton'); const addReportButtonEmpty = document.getElementById('addReportButtonEmpty'); // Load reports on page load loadReports(); // Open modal handlers if (addReportButton) { addReportButton.addEventListener('click', () => openModal()); } if (addReportButtonEmpty) { addReportButtonEmpty.addEventListener('click', () => openModal()); } // Close modal handlers if (closeModal) { closeModal.addEventListener('click', () => { modal.classList.add('hidden'); }); } if (cancelButton) { cancelButton.addEventListener('click', () => { modal.classList.add('hidden'); }); } // Form submission if (form) { form.addEventListener('submit', async (e) => { e.preventDefault(); const reportId = document.getElementById('reportId').value; // Convert datetime-local to UTC ISO string let reportDateUTC = undefined; const reportDateValue = document.getElementById('Report_Date').value; if (reportDateValue) { const localDate = new Date(reportDateValue); reportDateUTC = localDate.toISOString(); } const formData = { Employee: document.getElementById('Employee').value || undefined, Report: document.getElementById('Report').value || undefined, Centiment: document.getElementById('Centiment').value || undefined, Report_Date: reportDateUTC, Link_01: document.getElementById('Link_01').value || undefined, Link_02: document.getElementById('Link_02').value || undefined, Link_03: document.getElementById('Link_03').value || undefined, Link_04: document.getElementById('Link_04').value || undefined, Link_05: document.getElementById('Link_05').value || undefined, }; try { const token = getAuthToken(); const url = reportId ? `${API_BASE}/${reportId}` : API_BASE; const method = reportId ? '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 ${reportId ? 'updated' : 'created'} successfully.`, 'success'); modal.classList.add('hidden'); loadReports(reportId ? currentPage : 1); } catch (error) { console.error('Error saving report:', error); showMessage(error.message || 'Failed to save report.', 'error'); } }); } // Load employees when page loads loadEmployees(); }); // Utility function to escape HTML function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }