HR AIOPEN PORTAL

Secure email tracking and assignment system

Admin Portal

System configuration, user management & email assignment

Agent Portal

Email processing, task management & monitoring

} function closeLeaveEditModal() { document.getElementById('leaveEditModal').style.display = 'none'; } async function saveLeaveRequest(e) { e.preventDefault(); const btn = document.getElementById('saveLeaveBtn'); btn.disabled = true; btn.innerHTML = ' Saving...'; const formData = new FormData(e.target); try { const res = await fetch(`${API_BASE}?action=update_leave_request`, { method: 'POST', headers: { 'Authorization': `Bearer ${authToken}` }, body: formData }); const data = await res.json(); if (data.success) { alert('Leave request updated successfully'); closeLeaveEditModal(); loadLeaveRequests(); } else { alert(data.error || 'Failed to update request'); } } catch (err) { alert('Error updating request'); } finally { btn.disabled = false; btn.textContent = 'Save Changes'; } } async function testSmtpConfig() { if (!authToken) return; const email = prompt("Enter email address to receive the test message:", currentUser.email || ""); if (email === null) return; // Cancelled if (!email || !email.includes('@')) { alert("Please enter a valid email address."); return; } try { const response = await fetch(`${API_BASE}?action=test_smtp`, { method: 'POST', headers: { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/x-www-form-urlencoded' }, body: `email=${encodeURIComponent(email)}` }); const data = await response.json(); if (data.success) { alert(data.message || 'Test email sent successfully!'); } else { alert('SMTP Test Failed: ' + (data.error || 'Unknown error')); } } catch (e) { alert('Network error during SMTP test.'); } } async function sendApprovalEmail(id) { const email = prompt("Enter the email address to send the approval request to:"); if (email === null) return; // Cancelled if (!email || !email.includes('@')) { alert("Please enter a valid email address."); return; } try { const res = await fetch(`${API_BASE}?action=send_approval_email`, { method: 'POST', headers: { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/x-www-form-urlencoded' }, body: `id=${id}&email=${encodeURIComponent(email)}` }); const data = await res.json(); if (data.success) { alert('Approval email sent successfully!'); } else { alert(data.error || 'Failed to send email'); } } catch (e) { alert('Network error'); } } function toggleSelectAllLeaves(source) { const checkboxes = document.querySelectorAll('.leave-checkbox'); checkboxes.forEach(cb => cb.checked = source.checked); } async function sendBulkApprovalEmail() { const checkboxes = document.querySelectorAll('.leave-checkbox:checked'); if (checkboxes.length === 0) { alert('Please select at least one pending request.'); return; } const ids = Array.from(checkboxes).map(cb => cb.value); const email = prompt("Enter the email address to send the approval request to:"); if (email === null) return; if (!email || !email.includes('@')) { alert("Invalid email."); return; } try { const res = await fetch(`${API_BASE}?action=send_approval_email`, { method: 'POST', headers: { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ ids: ids, email: email }) }); const data = await res.json(); if (data.success) { alert('Approval email sent successfully!'); } else { alert(data.error || 'Failed to send email'); } } catch (e) { alert('Network error'); } } // --- Email Accounts Management --- async function loadEmailAccountsConfig() { const tbody = document.getElementById('emailAccountsTableBody'); tbody.innerHTML = 'Loading...'; try { const res = await fetch(`${API_BASE}?action=get_email_accounts_config`, { headers: { 'Authorization': `Bearer ${authToken}` } }); const data = await res.json(); if (data.success) { tbody.innerHTML = data.accounts.map(acc => ` ${acc.site_name} ${acc.email} ${acc.host}:${acc.port} ${acc.imap_host} ${acc.active == 1 ? 'Yes' : 'No'} `).join(''); } } catch (e) { tbody.innerHTML = 'Error loading accounts'; } } function openEmailAccountModal(acc = null) { document.getElementById('emailAccountModal').style.display = 'flex'; document.getElementById('emailAccountModalTitle').textContent = acc ? 'Edit Account' : 'Add Account'; document.getElementById('accId').value = acc ? acc.id : ''; document.getElementById('accSiteName').value = acc ? acc.site_name : ''; document.getElementById('accEmail').value = acc ? acc.email : ''; document.getElementById('accPassword').value = ''; // Don't show password document.getElementById('accHost').value = acc ? acc.host : 'smtp.gmail.com'; document.getElementById('accPort').value = acc ? acc.port : '465'; document.getElementById('accImapHost').value = acc ? acc.imap_host : 'imap.gmail.com'; document.getElementById('accActive').value = acc ? acc.active : '1'; } async function saveEmailAccount(e) { e.preventDefault(); const formData = new FormData(); formData.append('id', document.getElementById('accId').value); formData.append('site_name', document.getElementById('accSiteName').value); formData.append('email', document.getElementById('accEmail').value); formData.append('password', document.getElementById('accPassword').value); formData.append('host', document.getElementById('accHost').value); formData.append('port', document.getElementById('accPort').value); formData.append('imap_host', document.getElementById('accImapHost').value); formData.append('active', document.getElementById('accActive').value); try { const res = await fetch(`${API_BASE}?action=save_email_account`, { method: 'POST', headers: { 'Authorization': `Bearer ${authToken}` }, body: formData }); const data = await res.json(); if (data.success) { document.getElementById('emailAccountModal').style.display = 'none'; loadEmailAccountsConfig(); } else alert(data.error); } catch (e) { alert('Error saving account'); } } async function deleteEmailAccount(id) { if (!confirm('Delete this account?')) return; try { const res = await fetch(`${API_BASE}?action=delete_email_account`, { method: 'POST', headers: { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/x-www-form-urlencoded' }, body: `id=${id}` }); if ((await res.json()).success) loadEmailAccountsConfig(); } catch (e) { alert('Error deleting account'); } } // --- Email Report Functions --- async function loadAgentsForReport() { if (!authToken) return; const select = document.getElementById('reportAgentFilter'); if (select.options.length > 1) return; try { const response = await fetch(`${API_BASE}?action=get_agents`, { headers: { 'Authorization': `Bearer ${authToken}` } }); const data = await response.json(); if (data.success) { data.agents.forEach(agent => { const option = document.createElement('option'); option.value = agent.id; option.textContent = agent.name; select.appendChild(option); }); } } catch (e) { console.error(e); } } async function loadEmailReport(page = 1) { if (!authToken) return; const agentId = document.getElementById('reportAgentFilter').value; const startDate = document.getElementById('reportStartDate').value; const endDate = document.getElementById('reportEndDate').value; const limit = document.getElementById('reportLimit').value; const container = document.getElementById('reportContainer'); const loading = document.getElementById('reportLoading'); loading.style.display = 'block'; container.innerHTML = ''; try { let url = `${API_BASE}?action=get_email_report&page=${page}&limit=${limit}`; if (agentId !== 'all') url += `&agent_id=${agentId}`; if (startDate) url += `&start_date=${startDate}`; if (endDate) url += `&end_date=${endDate}`; const res = await fetch(url, { headers: { 'Authorization': `Bearer ${authToken}` } }); const data = await res.json(); if (data.success && data.report.length > 0) { // Generate Summary Cards let summaryHtml = '
'; if (data.summary) { for (const [agent, count] of Object.entries(data.summary)) { summaryHtml += `
${agent}
${count}
`; } } summaryHtml += '
'; let html = summaryHtml + `
Total Records: ${data.pagination.total} (Page ${data.pagination.page} of ${data.pagination.pages})
`; data.report.forEach(row => { html += ` `; }); html += '
Agent Subject Sender Received Assigned Completed Duration Status
${row.agent_name} ${row.subject} ${row.sender_email} ${new Date(row.received_at).toLocaleString()} ${new Date(row.assigned_at).toLocaleString()} ${row.completed_at ? new Date(row.completed_at).toLocaleString() : '-'} ${row.handling_time_formatted} ${row.email_status}
'; // Pagination Controls if (data.pagination.pages > 1) { html += `
Page ${data.pagination.page} of ${data.pagination.pages}
`; } container.innerHTML = html; } else { container.innerHTML = '

No records found for the selected criteria.

'; } } catch (e) { container.innerHTML = '

Error loading report.

'; } finally { loading.style.display = 'none'; } } function exportReportToCSV() { const table = document.getElementById('reportTable'); if (!table) { alert('No data to export'); return; } let csv = []; const rows = table.querySelectorAll('tr'); for (let i = 0; i < rows.length; i++) { let row = [], cols = rows[i].querySelectorAll('td, th'); for (let j = 0; j < cols.length; j++) { let data = cols[j].innerText.replace(/(\r\n|\n|\r)/gm, '').replace(/(\s\s)/gm, ' '); data = data.replace(/"/g, '""'); row.push('"' + data + '"'); } csv.push(row.join(',')); } const csvFile = new Blob([csv.join('\n')], {type: 'text/csv'}); const downloadLink = document.createElement('a'); downloadLink.download = 'email_report_' + new Date().toISOString().slice(0,10) + '.csv'; downloadLink.href = window.URL.createObjectURL(csvFile); downloadLink.style.display = 'none'; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); } // --- Chat Logic --- async function loadChatContacts(silent = false) { try { const res = await fetch(`${API_BASE}?action=get_chat_contacts`, { headers: { 'Authorization': `Bearer ${authToken}` } }); const data = await res.json(); if (data.success) { const list = document.getElementById('chatContactsList'); let html = '
Groups
'; data.groups.forEach(g => { const activeClass = (currentChat.id == g.id && currentChat.type == 'group') ? 'background:#e9ecef;' : ''; const editBtn = (currentUser.role === 'admin') ? `` : ''; html += `
${g.name} ${editBtn}
`; }); html += '
Users
'; data.users.forEach(u => { const activeClass = (currentChat.id == u.id && currentChat.type == 'user') ? 'background:#e9ecef;' : ''; const badge = u.unread > 0 ? `${u.unread}` : ''; html += `
${u.name} ${badge}
`; }); list.innerHTML = html; // Populate group creation list if empty const groupList = document.getElementById('groupMembersList'); if (groupList.innerHTML === '') { groupList.innerHTML = data.users.map(u => `
`).join(''); } } } catch (e) {} } function openChat(id, type, name) { currentChat = { id, type, name }; document.getElementById('chatHeader').textContent = (type === 'group' ? 'Group: ' : 'Chat with: ') + name; loadChatMessages(); loadChatContacts(true); // Refresh to clear unread } async function loadChatMessages(silent = false) { if (!currentChat.id) return; const container = document.getElementById('chatMessages'); if (!silent) container.innerHTML = '
'; try { const res = await fetch(`${API_BASE}?action=get_chat_messages&contact_id=${currentChat.id}&is_group=${currentChat.type === 'group'}`, { headers: { 'Authorization': `Bearer ${authToken}` } }); const data = await res.json(); if (data.success) { const html = data.messages.map(m => { const isMe = m.sender_id == currentUser.id; return `
${isMe ? 'You' : m.sender_name}
${m.message}
${new Date(m.created_at).toLocaleTimeString()}
`; }).join(''); container.innerHTML = html; if (!silent) container.scrollTop = container.scrollHeight; } } catch (e) {} } async function sendChatMessage() { const input = document.getElementById('chatInput'); const msg = input.value.trim(); if (!msg || !currentChat.id) return; const payload = { message: msg }; if (currentChat.type === 'group') payload.group_id = currentChat.id; else payload.recipient_id = currentChat.id; input.value = ''; // Clear immediately try { await fetch(`${API_BASE}?action=send_chat_message`, { method: 'POST', headers: { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); loadChatMessages(true); } catch (e) {} } function openCreateGroupModal() { document.getElementById('createGroupModal').style.display = 'flex'; } async function createGroup() { const name = document.getElementById('newGroupName').value; const checks = document.querySelectorAll('.group-member-check:checked'); const members = Array.from(checks).map(c => c.value); if (!name) return alert('Enter group name'); try { const res = await fetch(`${API_BASE}?action=create_chat_group`, { method: 'POST', headers: { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name, members }) }); const data = await res.json(); if (data.success) { document.getElementById('createGroupModal').style.display = 'none'; document.getElementById('newGroupName').value = ''; loadChatContacts(); } else { alert(data.error); } } catch (e) {} } async function openEditGroupModal(groupId) { const modal = document.getElementById('editGroupModal'); modal.style.display = 'flex'; document.getElementById('editGroupId').value = groupId; const membersListDiv = document.getElementById('editGroupMembersList'); membersListDiv.innerHTML = '
'; try { // We need all users to populate the list, and group details to check the right boxes. const [groupRes, allUsersRes] = await Promise.all([ fetch(`${API_BASE}?action=get_chat_group_details&group_id=${groupId}`, { headers: { 'Authorization': `Bearer ${authToken}` } }), fetch(`${API_BASE}?action=get_chat_contacts`, { headers: { 'Authorization': `Bearer ${authToken}` } }) // This already returns all active users ]); const groupData = await groupRes.json(); const allUsersData = await allUsersRes.json(); if (groupData.success && allUsersData.success) { document.getElementById('editGroupName').value = groupData.group.name; const memberIds = groupData.members; const allUsers = allUsersData.users; membersListDiv.innerHTML = allUsers.map(u => `
`).join(''); } else { membersListDiv.innerHTML = '

Error loading group details.

'; } } catch (e) { membersListDiv.innerHTML = '

Network error.

'; } } async function updateGroup() { const groupId = document.getElementById('editGroupId').value; const name = document.getElementById('editGroupName').value; const checks = document.querySelectorAll('.edit-group-member-check:checked'); const members = Array.from(checks).map(c => parseInt(c.value)); if (!name) return alert('Group name is required.'); try { const res = await fetch(`${API_BASE}?action=update_chat_group`, { method: 'POST', headers: { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ group_id: groupId, name, members }) }); const data = await res.json(); if (data.success) { document.getElementById('editGroupModal').style.display = 'none'; loadChatContacts(); // If the currently open chat was this group, update its header if (currentChat.id == groupId && currentChat.type === 'group') { currentChat.name = name; document.getElementById('chatHeader').textContent = 'Group: ' + name; } } else { alert(data.error || 'Failed to update group.'); } } catch (e) { alert('Network error.'); } } async function deleteGroup() { const groupId = document.getElementById('editGroupId').value; const groupName = document.getElementById('editGroupName').value; if (!confirm(`Are you sure you want to delete the group "${groupName}"? This cannot be undone.`)) return; try { const res = await fetch(`${API_BASE}?action=delete_chat_group`, { method: 'POST', headers: { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ group_id: groupId }) }); const data = await res.json(); if (data.success) { document.getElementById('editGroupModal').style.display = 'none'; loadChatContacts(); // If the deleted group was the open chat, reset the chat view if (currentChat.id == groupId && currentChat.type === 'group') { currentChat = { id: null, type: null, name: null }; document.getElementById('chatHeader').textContent = 'Select a contact'; document.getElementById('chatMessages').innerHTML = ''; } } else { alert(data.error || 'Failed to delete group.'); } } catch (e) { alert('Network error.'); } } // Forgot Password Functions function openForgotModal() { document.getElementById('forgotModal').style.display = 'flex'; document.getElementById('forgotEmail').value = ''; } function closeForgotModal() { document.getElementById('forgotModal').style.display = 'none'; } async function handleForgotSubmit(e) { e.preventDefault(); const email = document.getElementById('forgotEmail').value; const btn = document.getElementById('forgotBtn'); btn.disabled = true; btn.textContent = 'Sending...'; try { const res = await fetch(`${API_BASE}?action=request_password_reset`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }) }); const data = await res.json(); if (data.success) { alert('Reset link sent to your email.'); closeForgotModal(); } else { alert(data.error || 'Failed to send link'); } } catch (err) { alert('Network error'); } finally { btn.disabled = false; btn.textContent = 'Send Reset Link'; } } // Utility functions function showMessage(form, type, message) { const element = document.getElementById(`${form}${type.charAt(0).toUpperCase() + type.slice(1)}`); element.textContent = message; element.style.display = 'block'; if (type === 'error') { element.style.background = '#fee'; element.style.color = '#d33'; } else { element.style.background = '#e8f7ee'; element.style.color = '#28a745'; } } function clearMessages(form) { document.getElementById(`${form}Error`).style.display = 'none'; document.getElementById(`${form}Success`).style.display = 'none'; } // NEW: Load recent assignment history async function loadRecentHistory() { if (!authToken) return; const dateFilter = document.getElementById('historyDateFilter').value; const loadingElement = document.getElementById('recentHistoryLoading'); const listElement = document.getElementById('recentHistoryList'); loadingElement.style.display = 'block'; listElement.innerHTML = ''; try { let url = `${API_BASE}?action=get_recent_history`; if (dateFilter) url += `&date=${dateFilter}`; const response = await fetch(url, { headers: { 'Authorization': `Bearer ${authToken}` } }); const data = await response.json(); if (data.success && data.history.length > 0) { let html = '
'; data.history.forEach(history => { const date = new Date(history.assigned_at).toLocaleString(); html += `
Email ID: ${history.email_id}
Subject: ${history.subject}
${history.agent_name}
Action: ${history.action_type || 'Assigned'}
${history.notes ? `
${history.notes}
` : ''}
${date}
`; }); html += '
'; listElement.innerHTML = html; } else { listElement.innerHTML = '

No recent history found.

'; } } catch (error) { listElement.innerHTML = '

Error loading history.

'; } finally { loadingElement.style.display = 'none'; } } // NEW: Fetch Gmail Emails async function fetchGmailEmails(btn) { if (!authToken) return; if (!confirm('Fetch new unseen emails from Gmail? This might take a moment.')) return; const originalText = btn.textContent; btn.disabled = true; btn.innerHTML = ' Fetching...'; try { const response = await fetch(`${API_BASE}?action=fetch_gmail`, { headers: { 'Authorization': `Bearer ${authToken}` } }); const data = await response.json(); if (data.success) { alert(`Successfully fetched ${data.count} new emails.`); refreshDashboard(); } else { alert('Error: ' + (data.error || 'Unknown error')); } } catch (e) { alert('Network error'); } finally { btn.disabled = false; btn.innerHTML = ' ' + originalText; } } // Initialize the application document.addEventListener('DOMContentLoaded', function() { // Dashboard init if (!authToken) { // Load sample agents for filter dropdown const agentFilter = document.getElementById('agentFilter'); const sampleAgents = [ /*{id: 1, name: 'John Doe'}, {id: 2, name: 'Jane Smith'}, {id: 3, name: 'Bob Johnson'} */ ]; sampleAgents.forEach(agent => { const option = document.createElement('option'); option.value = agent.id; option.textContent = agent.name; agentFilter.appendChild(option); }); } });