/** * IT Site Survey AI - Frontend Application * Single-page application with Surveys List, Create Survey, and AI Analysis tabs */ // ============================================ // State Management // ============================================ const AppState = { currentTab: 'surveys', currentSurveyId: null, surveys: [], surveyTemplate: null, uploadedPhotos: [], analysisContent: '', deleteTargetId: null, isGenerating: false }; // ============================================ // API Configuration // ============================================ const API_BASE_URL = window.location.origin; // ============================================ // Sample Data for Testing // ============================================ const SAMPLE_DATA = { name: "Acme Corporation Site Survey", client: "Acme Corporation", site: "Main Headquarters - San Francisco", description: "Comprehensive IT infrastructure assessment for digital transformation initiative. Planning cloud migration and security modernization.", model: "gpt-4o", answers: { "q1": "450 employees", "q2": "3 locations (SF HQ, Denver Office, Austin Office)", "q3": "1) Migrate 80% of workloads to cloud by end of 2025\n2) Implement Zero Trust security architecture\n3) Deploy AI-powered customer service platform", "q4": "Hybrid topology with core-distribution-access layers at HQ", "q5": "24 physical servers, 120 VMs", "q6": "VMware vSphere 7.0 with vCenter", "q7": "150TB usable, currently 78% utilized", "q8": "Single internet circuit at Denver office, no redundant power in Austin server room", "q9": "1Gbps symmetric at HQ, 500Mbps at Denver, 300Mbps at Austin", "q10": "45 switches, 8 routers (Cisco Catalyst and ISR series)", "q11": "Planning", "q12": "Mix of Wi-Fi 5 (802.11ac) and Wi-Fi 6 (802.11ax)", "q13": "12 VLANs - segmented by department (Finance, HR, Engineering, Sales, etc.)", "q14": "Palo Alto Networks PA-5220", "q15": "Yes", "q16": "CrowdStrike Falcon", "q17": "Yes - Critical Only", "q18": "6 months ago - Medium risk findings related to outdated Java applications", "q19": "Yes - Not Tested", "q20": "Veeam Backup & Replication, RTO: 4 hours, RPO: 1 hour", "q21": "AWS (primary), Azure (secondary for AD and O365)", "q22": "30% cloud, 70% on-premises", "q23": "Yes - Basic", "q24": "~$45,000/month - Optimization in progress with Reserved Instances", "q25": "1. SAP ERP\n2. Salesforce CRM\n3. Microsoft 365\n4. Custom e-commerce platform\n5. Workday HCM\n6. ServiceNow ITSM\n7. Confluence\n8. Jira\n9. Zoom\n10. Internal BI platform", "q26": "Legacy AS/400 system for inventory, needs API layer or replacement", "q27": "Microsoft Teams, Slack for engineering, Zoom for video", "q28": "Yes - Partial", "q29": "SOC2 Type II, ISO 27001, PCI-DSS (for e-commerce)", "q30": "Last SOC2 audit completed 2 months ago with minor findings remediated", "q31": "Yes - Implemented", "q32": "ServiceNow ITSM, Jira Service Management for dev teams", "q33": "1:75 ratio (6 IT staff for 450 users)", "q34": "4.2 hours average for L2 tickets, 15 minutes for L1", "q35": "1) Complete cloud migration\n2) Implement SD-WAN across all sites\n3) Deploy AI-driven security operations center" }, photos: [] }; // ============================================ // DOM Elements Cache // ============================================ const DOM = {}; function cacheDOMElements() { // Tabs DOM.tabButtons = document.querySelectorAll('.tab-btn'); DOM.tabContents = document.querySelectorAll('.tab-content'); // Navigation DOM.navSurveys = document.getElementById('nav-surveys'); DOM.navCreate = document.getElementById('nav-create'); // Tab content sections DOM.tabSurveys = document.getElementById('tab-surveys'); DOM.tabCreate = document.getElementById('tab-create'); DOM.tabAnalysis = document.getElementById('tab-analysis'); // Surveys List DOM.surveysList = document.getElementById('surveys-list'); DOM.btnClearAll = document.getElementById('btn-clear-all'); // Create Survey Form DOM.surveyForm = document.getElementById('survey-form'); DOM.surveyName = document.getElementById('survey-name'); DOM.surveyClient = document.getElementById('survey-client'); DOM.surveySite = document.getElementById('survey-site'); DOM.surveyDescription = document.getElementById('survey-description'); DOM.surveyModel = document.getElementById('survey-model'); DOM.questionsContainer = document.getElementById('questions-container'); DOM.questionCount = document.getElementById('question-count'); DOM.btnLoadSample = document.getElementById('btn-load-sample'); DOM.btnSubmitSurvey = document.getElementById('btn-submit-survey'); // Photo Upload DOM.photoUploadArea = document.getElementById('photo-upload-area'); DOM.photoInput = document.getElementById('photo-input'); DOM.btnBrowsePhotos = document.getElementById('btn-browse-photos'); DOM.photoPreviewGrid = document.getElementById('photo-preview-grid'); // Analysis Tab DOM.btnBackToSurveys = document.getElementById('btn-back-to-surveys'); DOM.analysisSurveyName = document.getElementById('analysis-survey-name'); DOM.analysisModel = document.getElementById('analysis-model'); DOM.btnGenerateAnalysis = document.getElementById('btn-generate-analysis'); DOM.analysisSurveyMeta = document.getElementById('analysis-survey-meta'); DOM.analysisQuestionSummary = document.getElementById('analysis-question-summary'); DOM.analysisOutput = document.getElementById('analysis-output'); DOM.analysisLoading = document.getElementById('analysis-loading'); DOM.exportButtons = document.getElementById('export-buttons'); DOM.btnExportText = document.getElementById('btn-export-text'); DOM.btnExportPdf = document.getElementById('btn-export-pdf'); DOM.btnEmail = document.getElementById('btn-email'); // Chat DOM.chatMessages = document.getElementById('chat-messages'); DOM.chatInput = document.getElementById('chat-input'); DOM.btnSendChat = document.getElementById('btn-send-chat'); // Modals DOM.deleteModal = document.getElementById('delete-modal'); DOM.btnCancelDelete = document.getElementById('btn-cancel-delete'); DOM.btnConfirmDelete = document.getElementById('btn-confirm-delete'); DOM.clearAllModal = document.getElementById('clear-all-modal'); DOM.btnCancelClearAll = document.getElementById('btn-cancel-clear-all'); DOM.btnConfirmClearAll = document.getElementById('btn-confirm-clear-all'); // Toast DOM.toast = document.getElementById('toast'); DOM.toastMessage = document.getElementById('toast-message'); } // ============================================ // API Functions // ============================================ async function fetchAPI(endpoint, options = {}) { const url = `${API_BASE_URL}${endpoint}`; const response = await fetch(url, { headers: { 'Content-Type': 'application/json', ...options.headers }, ...options }); if (!response.ok) { const error = await response.json().catch(() => ({})); throw new Error(error.error || `HTTP ${response.status}`); } return response.json(); } async function loadSurveyTemplate() { try { const data = await fetchAPI('/api/surveys/template'); AppState.surveyTemplate = data; renderQuestions(); } catch (error) { showToast('Failed to load survey template', 'error'); console.error('Error loading template:', error); } } async function loadSurveys() { try { const data = await fetchAPI('/api/surveys'); AppState.surveys = data.surveys; renderSurveysList(); } catch (error) { showToast('Failed to load surveys', 'error'); console.error('Error loading surveys:', error); } } async function createSurvey(surveyData) { try { const data = await fetchAPI('/api/surveys', { method: 'POST', body: JSON.stringify(surveyData) }); showToast('Survey created successfully', 'success'); return data.id; } catch (error) { showToast('Failed to create survey', 'error'); throw error; } } async function deleteSurvey(surveyId) { try { await fetchAPI(`/api/surveys/${surveyId}`, { method: 'DELETE' }); showToast('Survey deleted successfully', 'success'); } catch (error) { showToast('Failed to delete survey', 'error'); throw error; } } async function deleteAllSurveys() { try { await fetchAPI('/api/surveys', { method: 'DELETE' }); showToast('All surveys deleted', 'success'); } catch (error) { showToast('Failed to delete surveys', 'error'); throw error; } } async function getSurvey(surveyId) { try { return await fetchAPI(`/api/surveys/${surveyId}`); } catch (error) { showToast('Failed to load survey details', 'error'); throw error; } } async function exportSurvey(surveyId, format, content) { try { const data = await fetchAPI(`/api/surveys/${surveyId}/export`, { method: 'POST', body: JSON.stringify({ format, content }) }); return data; } catch (error) { showToast('Failed to export survey', 'error'); throw error; } } // ============================================ // UI Functions // ============================================ function switchTab(tabName) { // Update state AppState.currentTab = tabName; // Update tab buttons document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.tab === tabName); }); // Update tab contents document.querySelectorAll('.tab-content').forEach(content => { content.classList.toggle('active', content.id === `tab-${tabName}`); }); // Special handling for analysis tab visibility if (tabName === 'analysis') { document.getElementById('tab-analysis').classList.add('active'); } // Update navigation document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.tab === tabName); }); } function showToast(message, type = 'success') { DOM.toastMessage.textContent = message; DOM.toast.className = `toast ${type}`; DOM.toast.style.display = 'block'; setTimeout(() => { DOM.toast.style.display = 'none'; }, 3000); } function showModal(modal) { modal.style.display = 'flex'; } function hideModal(modal) { modal.style.display = 'none'; } // ============================================ // Survey List Functions // ============================================ function renderSurveysList() { if (AppState.surveys.length === 0) { DOM.surveysList.innerHTML = `
No surveys yet. Create your first survey!
Click "Generate Recommendations" to get AI-powered insights
Failed to generate analysis. Please try again.
Click "Generate Recommendations" to get AI-powered insights
$1')
.replace(/^\s*[-*+]\s+(.*$)/gim, '${escapeHtml(content)}
`; DOM.chatMessages.appendChild(messageDiv); DOM.chatMessages.scrollTop = DOM.chatMessages.scrollHeight; } // ============================================ // Export Functions // ============================================ async function exportToText() { if (!AppState.currentSurveyId) return; try { const data = await exportSurvey(AppState.currentSurveyId, 'text', AppState.analysisContent); downloadFile(data.filename, data.content, 'text/plain'); showToast('Report exported as text', 'success'); } catch (error) { console.error('Export error:', error); } } async function exportToPDF() { if (!AppState.currentSurveyId) return; try { const data = await exportSurvey(AppState.currentSurveyId, 'pdf', AppState.analysisContent); // Decode base64 PDF content const pdfContent = atob(data.content); const bytes = new Uint8Array(pdfContent.length); for (let i = 0; i < pdfContent.length; i++) { bytes[i] = pdfContent.charCodeAt(i); } // Create blob and download const blob = new Blob([bytes], { type: 'application/pdf' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = data.filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showToast('PDF report downloaded', 'success'); } catch (error) { console.error('PDF Export error:', error); showToast('Failed to export PDF', 'error'); } } async function emailReport() { if (!AppState.currentSurveyId) return; try { const data = await exportSurvey(AppState.currentSurveyId, 'email', AppState.analysisContent); // Open email client with mailto link if (data.mailto_link) { window.open(data.mailto_link, '_blank'); showToast('Email client opened', 'success'); } else { showToast('Failed to generate email link', 'error'); } } catch (error) { console.error('Email error:', error); showToast('Failed to open email', 'error'); } } function downloadFile(filename, content, mimeType) { const blob = new Blob([content], { type: mimeType }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } // ============================================ // Utility Functions // ============================================ function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function formatDate(dateString) { if (!dateString) return 'Unknown'; const date = new Date(dateString); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } // ============================================ // Event Listeners Setup // ============================================ function setupEventListeners() { // Tab navigation document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', () => { switchTab(btn.dataset.tab); }); }); // Create survey form DOM.surveyForm.addEventListener('submit', handleFormSubmit); DOM.btnLoadSample.addEventListener('click', loadSampleData); // Photo upload DOM.btnBrowsePhotos.addEventListener('click', () => DOM.photoInput.click()); DOM.photoInput.addEventListener('change', (e) => handlePhotoUpload(e.target.files)); DOM.photoUploadArea.addEventListener('dragover', (e) => { e.preventDefault(); DOM.photoUploadArea.classList.add('dragover'); }); DOM.photoUploadArea.addEventListener('dragleave', () => { DOM.photoUploadArea.classList.remove('dragover'); }); DOM.photoUploadArea.addEventListener('drop', (e) => { e.preventDefault(); DOM.photoUploadArea.classList.remove('dragover'); handlePhotoUpload(e.dataTransfer.files); }); DOM.photoUploadArea.addEventListener('click', (e) => { if (e.target === DOM.photoUploadArea || e.target.closest('svg') || e.target.tagName === 'P') { DOM.photoInput.click(); } }); // Clear all surveys DOM.btnClearAll.addEventListener('click', () => { showModal(DOM.clearAllModal); }); DOM.btnCancelClearAll.addEventListener('click', () => { hideModal(DOM.clearAllModal); }); DOM.btnConfirmClearAll.addEventListener('click', async () => { hideModal(DOM.clearAllModal); try { await deleteAllSurveys(); await loadSurveys(); } catch (error) { console.error('Error clearing surveys:', error); } }); // Delete survey modal DOM.btnCancelDelete.addEventListener('click', () => { hideModal(DOM.deleteModal); AppState.deleteTargetId = null; }); DOM.btnConfirmDelete.addEventListener('click', async () => { if (AppState.deleteTargetId) { hideModal(DOM.deleteModal); try { await deleteSurvey(AppState.deleteTargetId); await loadSurveys(); } catch (error) { console.error('Error deleting survey:', error); } AppState.deleteTargetId = null; } }); // Analysis tab DOM.btnBackToSurveys.addEventListener('click', () => { switchTab('surveys'); }); DOM.btnGenerateAnalysis.addEventListener('click', generateAnalysis); // Chat DOM.btnSendChat.addEventListener('click', sendChatMessage); DOM.chatInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { sendChatMessage(); } }); // Export DOM.btnExportText.addEventListener('click', exportToText); DOM.btnExportPdf.addEventListener('click', exportToPDF); DOM.btnEmail.addEventListener('click', emailReport); } // ============================================ // Initialization // ============================================ // Load available AI models from API async function loadModels() { try { const data = await fetchAPI('/api/models'); if (data.models && data.models.length > 0) { const surveySelect = document.getElementById('survey-model'); const analysisSelect = document.getElementById('analysis-model'); // Clear loading option surveySelect.innerHTML = ''; analysisSelect.innerHTML = ''; // Add models to both dropdowns data.models.forEach(model => { const option = document.createElement('option'); option.value = model.id; option.textContent = model.name; surveySelect.appendChild(option.cloneNode(true)); analysisSelect.appendChild(option); }); // Set default if (data.default) { surveySelect.value = data.default; analysisSelect.value = data.default; } } } catch (error) { console.error('Failed to load models:', error); // Keep default options if API fails } } function init() { cacheDOMElements(); setupEventListeners(); loadModels(); // Load available AI models loadSurveyTemplate(); loadSurveys(); } // Start the app when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); }