/** * Contact Management Admin Page * View and manage contact form submissions */ let allContacts = []; let currentContact = null; // Initialize page document.addEventListener('DOMContentLoaded', async () => { await loadStats(); await loadContacts(); setupEventListeners(); }); /** * Load statistics */ async function loadStats() { try { const token = localStorage.getItem('admin_token'); const response = await fetch('/api/contact/admin/stats', { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) throw new Error('Failed to load stats'); const data = await response.json(); const stats = data.stats; document.getElementById('stat-total').textContent = stats.total; document.getElementById('stat-new').textContent = stats.by_status.new; document.getElementById('stat-assigned').textContent = stats.by_status.assigned; document.getElementById('stat-responded').textContent = stats.by_status.responded; } catch (error) { console.error('Error loading stats:', error); } } /** * Load contacts */ async function loadContacts() { try { const token = localStorage.getItem('admin_token'); const status = document.getElementById('filter-status').value; const type = document.getElementById('filter-type').value; const priority = document.getElementById('filter-priority').value; let url = '/api/contact/admin/list?limit=50'; if (status) url += `&status=${status}`; if (type) url += `&type=${type}`; if (priority) url += `&priority=${priority}`; const response = await fetch(url, { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) throw new Error('Failed to load contacts'); const data = await response.json(); allContacts = data.contacts; renderContacts(); } catch (error) { console.error('Error loading contacts:', error); showError('Failed to load contacts'); } } /** * Render contacts list */ function renderContacts() { const container = document.getElementById('contacts-container'); if (allContacts.length === 0) { container.innerHTML = \`

No contacts found matching the selected filters.

\`; return; } container.innerHTML = allContacts.map(contact => \`

\${escapeHtml(contact.contact.name)}

\${getTypeBadge(contact.type)} \${getPriorityBadge(contact.priority)} \${getStatusBadge(contact.status)}

Email: \${escapeHtml(contact.contact.email)} \${contact.contact.organization ? \` | Org: \${escapeHtml(contact.contact.organization)}\` : ''}

\${contact.inquiry.subject ? \`

\${escapeHtml(contact.inquiry.subject)}

\` : ''}

\${escapeHtml(contact.inquiry.message)}

\${formatDate(contact.created_at)}
\${contact.response.sent_at ? \`
Responded \${formatDate(contact.response.sent_at)}
\` : ''}
\`).join(''); // Add click listeners container.querySelectorAll('[data-contact-id]').forEach(el => { el.addEventListener('click', () => { const contactId = el.getAttribute('data-contact-id'); showContactDetail(contactId); }); }); } // ... rest of file continues in next command /** * Show contact detail modal */ async function showContactDetail(contactId) { try { const token = localStorage.getItem('admin_token'); const response = await fetch(\`/api/contact/admin/\${contactId}\`, { headers: { 'Authorization': \`Bearer \${token}\` } }); if (!response.ok) throw new Error('Failed to load contact'); const data = await response.json(); currentContact = data.contact; renderContactDetail(currentContact); document.getElementById('contact-detail-modal').classList.remove('hidden'); } catch (error) { console.error('Error loading contact detail:', error); alert('Failed to load contact details'); } } /** * Render contact detail */ function renderContactDetail(contact) { const content = document.getElementById('contact-detail-content'); content.innerHTML = \`

Contact Information

Name:
\${escapeHtml(contact.contact.name)}
Email:
\${contact.contact.organization ? \`
Organization:
\${escapeHtml(contact.contact.organization)}
\` : ''} \${contact.contact.phone ? \`
Phone:
\${escapeHtml(contact.contact.phone)}
\` : ''}

Inquiry

\${getTypeBadge(contact.type)} \${getPriorityBadge(contact.priority)} \${getStatusBadge(contact.status)}
\${contact.inquiry.subject ? \`
Subject:
\${escapeHtml(contact.inquiry.subject)}
\` : ''}
Message:
\${escapeHtml(contact.inquiry.message)}

Metadata

Submitted: \${formatDate(contact.created_at)}
Source: \${contact.source}
\${contact.metadata.source_page ? \`
Source Page: \${contact.metadata.source_page}
\` : ''} \${contact.metadata.ip ? \`
IP: \${contact.metadata.ip}
\` : ''}
\${contact.response.sent_at ? \`

Response

Responded: \${formatDate(contact.response.sent_at)}
\${contact.response.content ? \`
\${escapeHtml(contact.response.content)}
\` : ''}
\` : ''}
\${contact.status === 'new' ? \` \` : ''} \${contact.status === 'assigned' ? \` \` : ''} \${contact.status !== 'closed' ? \` \` : ''}
\`; } /** * Update contact status */ async function updateContactStatus(contactId, newStatus) { try { const token = localStorage.getItem('admin_token'); const response = await fetch(\`/api/contact/admin/\${contactId}\`, { method: 'PUT', headers: { 'Authorization': \`Bearer \${token}\`, 'Content-Type': 'application/json' }, body: JSON.stringify({ status: newStatus }) }); if (!response.ok) throw new Error('Failed to update status'); await loadStats(); await loadContacts(); closeDetailModal(); } catch (error) { console.error('Error updating status:', error); alert('Failed to update status'); } } /** * Mark as responded */ async function markAsResponded(contactId) { const responseContent = prompt('Enter response summary (optional):'); try { const token = localStorage.getItem('admin_token'); const response = await fetch(\`/api/contact/admin/\${contactId}/respond\`, { method: 'POST', headers: { 'Authorization': \`Bearer \${token}\`, 'Content-Type': 'application/json' }, body: JSON.stringify({ content: responseContent || 'Responded via email' }) }); if (!response.ok) throw new Error('Failed to mark as responded'); await loadStats(); await loadContacts(); closeDetailModal(); } catch (error) { console.error('Error marking as responded:', error); alert('Failed to mark as responded'); } } /** * Delete contact */ async function deleteContact(contactId) { if (!confirm('Are you sure you want to delete this contact? This cannot be undone.')) { return; } try { const token = localStorage.getItem('admin_token'); const response = await fetch(\`/api/contact/admin/\${contactId}\`, { method: 'DELETE', headers: { 'Authorization': \`Bearer \${token}\` } }); if (!response.ok) throw new Error('Failed to delete contact'); await loadStats(); await loadContacts(); closeDetailModal(); } catch (error) { console.error('Error deleting contact:', error); alert('Failed to delete contact'); } } /** * Setup event listeners */ function setupEventListeners() { document.getElementById('filter-status').addEventListener('change', loadContacts); document.getElementById('filter-type').addEventListener('change', loadContacts); document.getElementById('filter-priority').addEventListener('change', loadContacts); document.getElementById('refresh-btn').addEventListener('click', () => { loadStats(); loadContacts(); }); document.getElementById('close-detail-modal').addEventListener('click', closeDetailModal); document.getElementById('close-detail-btn').addEventListener('click', closeDetailModal); document.getElementById('contact-detail-modal').addEventListener('click', (e) => { if (e.target.id === 'contact-detail-modal') { closeDetailModal(); } }); } /** * Close detail modal */ function closeDetailModal() { document.getElementById('contact-detail-modal').classList.add('hidden'); currentContact = null; } /** * Utility: Get type badge */ function getTypeBadge(type) { const colors = { general: 'bg-gray-100 text-gray-800', partnership: 'bg-purple-100 text-purple-800', technical: 'bg-blue-100 text-blue-800', feedback: 'bg-green-100 text-green-800' }; return \`\${type}\`; } /** * Utility: Get priority badge */ function getPriorityBadge(priority) { const colors = { low: 'bg-gray-100 text-gray-600', normal: 'bg-blue-100 text-blue-800', high: 'bg-red-100 text-red-800' }; return \`\${priority}\`; } /** * Utility: Get status badge */ function getStatusBadge(status) { const colors = { new: 'bg-orange-100 text-orange-800', assigned: 'bg-blue-100 text-blue-800', responded: 'bg-green-100 text-green-800', closed: 'bg-gray-100 text-gray-600' }; return \`\${status}\`; } /** * Utility: Format date */ function formatDate(dateString) { if (!dateString) return 'N/A'; const date = new Date(dateString); return date.toLocaleString('en-NZ', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } /** * Utility: Escape HTML */ function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** * Utility: Show error */ function showError(message) { const container = document.getElementById('contacts-container'); container.innerHTML = \`

\${escapeHtml(message)}

\`; }