document.addEventListener('DOMContentLoaded', function() { // Document Elements const uploadArea = document.getElementById('upload-area'); const fileInput = document.getElementById('file-input'); const fileInfo = document.getElementById('file-info'); const fileName = document.getElementById('file-name'); const fileSize = document.getElementById('file-size'); const uploadButton = document.getElementById('upload-button'); const cancelButton = document.getElementById('cancel-button'); const noDocuments = document.getElementById('no-documents'); const documentsList = document.getElementById('documents-list'); const reportSection = document.getElementById('report-section'); const reportContainer = document.getElementById('report-container'); const closeReportButton = document.getElementById('close-report-button'); const loadingOverlay = document.getElementById('loading-overlay'); // Standards Elements const uploadStandardButton = document.getElementById('upload-standard-button'); const standardsUpload = document.getElementById('standards-upload'); const standardUploadArea = document.getElementById('standard-upload-area'); const standardFileInput = document.getElementById('standard-file-input'); const standardFileInfo = document.getElementById('standard-file-info'); const standardFileName = document.getElementById('standard-file-name'); const standardFileSize = document.getElementById('standard-file-size'); const standardUploadButton = document.getElementById('standard-upload-button'); const standardCancelButton = document.getElementById('standard-cancel-button'); const noStandards = document.getElementById('no-standards'); const standardsList = document.getElementById('standards-list'); // API endpoint base URL const API_BASE_URL = '/api'; // Local storage keys const DOCUMENTS_STORAGE_KEY = 'specscomply_documents'; const STANDARDS_STORAGE_KEY = 'specscomply_standards'; // Drag and drop functionality uploadArea.addEventListener('dragover', function(e) { e.preventDefault(); uploadArea.classList.add('dragover'); }); uploadArea.addEventListener('dragleave', function() { uploadArea.classList.remove('dragover'); }); uploadArea.addEventListener('drop', function(e) { e.preventDefault(); uploadArea.classList.remove('dragover'); if (e.dataTransfer.files.length) { handleFileSelection(e.dataTransfer.files[0]); } }); // File input change fileInput.addEventListener('change', function() { if (fileInput.files.length) { handleFileSelection(fileInput.files[0]); } }); // Upload button click uploadButton.addEventListener('click', function() { if (fileInput.files.length) { uploadDocument(fileInput.files[0]); } }); // Cancel button click cancelButton.addEventListener('click', function() { resetFileInput(); }); // Close report button click closeReportButton.addEventListener('click', function() { reportSection.style.display = 'none'; }); // Load stored documents and standards on page load loadDocuments(); loadStandards(); // Standards upload button click uploadStandardButton.addEventListener('click', function() { standardsUpload.style.display = 'block'; }); // Standard drag and drop functionality standardUploadArea.addEventListener('dragover', function(e) { e.preventDefault(); standardUploadArea.classList.add('dragover'); }); standardUploadArea.addEventListener('dragleave', function() { standardUploadArea.classList.remove('dragover'); }); standardUploadArea.addEventListener('drop', function(e) { e.preventDefault(); standardUploadArea.classList.remove('dragover'); if (e.dataTransfer.files.length) { handleStandardFileSelection(e.dataTransfer.files[0]); } }); // Standard file input change standardFileInput.addEventListener('change', function() { if (standardFileInput.files.length) { handleStandardFileSelection(standardFileInput.files[0]); } }); // Standard upload button click standardUploadButton.addEventListener('click', function() { if (standardFileInput.files.length) { uploadStandard(standardFileInput.files[0]); } }); // Standard cancel button click standardCancelButton.addEventListener('click', function() { resetStandardFileInput(); }); // Handle file selection function handleFileSelection(file) { // Update file info display fileName.textContent = file.name; fileSize.textContent = formatFileSize(file.size); // Show file info section uploadArea.style.display = 'none'; fileInfo.style.display = 'block'; } // Reset file input function resetFileInput() { fileInput.value = ''; uploadArea.style.display = 'block'; fileInfo.style.display = 'none'; } // Format file size function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } // Upload document async function uploadDocument(file) { try { // Show loading overlay loadingOverlay.style.display = 'flex'; const formData = new FormData(); formData.append('file', file); const response = await fetch(`${API_BASE_URL}/documents/upload`, { method: 'POST', body: formData }); if (!response.ok) { throw new Error(`Error uploading document: ${response.statusText}`); } const data = await response.json(); console.log('Upload response:', data); // Store document in local storage const document = { id: data.document_id, name: file.name, status: data.status, date: new Date().toISOString(), size: file.size }; saveDocument(document); resetFileInput(); loadDocuments(); // This should now work correctly checkDocumentStatus(data.document_id); } catch (error) { console.error('Error uploading document:', error); alert('Failed to upload document. Please try again.'); } finally { loadingOverlay.style.display = 'none'; } } // Save document to local storage function saveDocument(document) { let documents = JSON.parse(localStorage.getItem(DOCUMENTS_STORAGE_KEY) || '[]'); // Check if document already exists const existingIndex = documents.findIndex(doc => doc.id === document.id); if (existingIndex !== -1) { // Update existing document documents[existingIndex] = {...documents[existingIndex], ...document}; } else { // Add new document documents.push(document); } // Sort documents by date (newest first) documents.sort((a, b) => new Date(b.date) - new Date(a.date)); // Keep only the 10 most recent documents if (documents.length > 10) { documents = documents.slice(0, 10); } // Save to local storage localStorage.setItem(DOCUMENTS_STORAGE_KEY, JSON.stringify(documents)); } // Load documents from local storage function loadDocuments() { const documents = JSON.parse(localStorage.getItem(DOCUMENTS_STORAGE_KEY) || '[]'); // Clear documents list documentsList.innerHTML = ''; if (documents.length === 0) { noDocuments.style.display = 'block'; return; } noDocuments.style.display = 'none'; // Add documents to list documents.forEach(doc => { // Changed parameter name to 'doc' const li = createDocumentListItem(doc); documentsList.appendChild(li); }); } // Create document list item function createDocumentListItem(doc) { // Changed parameter name to 'doc' try { const li = window.document.createElement('li'); // Use window.document to be explicit li.className = 'document-item'; let dateStr = 'Unknown date'; try { dateStr = new Date(doc.date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } catch (e) { console.warn('Error formatting date:', e); } li.innerHTML = `

${doc.name || 'Unnamed document'}

${dateStr}

`; // Add event listeners to buttons const viewButton = li.querySelector('.view-button'); const resubmitButton = li.querySelector('.resubmit-button'); viewButton.addEventListener('click', function() { const documentId = this.getAttribute('data-id'); viewDocumentReport(documentId); }); resubmitButton.addEventListener('click', function() { const documentId = this.getAttribute('data-id'); resubmitDocument(documentId); }); return li; } catch (error) { console.error('Error creating document list item:', error); const li = window.document.createElement('li'); // Use window.document here too li.className = 'document-item'; li.textContent = 'Error displaying document'; return li; } } // View document report async function viewDocumentReport(documentId) { try { // Show loading overlay loadingOverlay.style.display = 'flex'; // Fetch document analysis const response = await fetch(`${API_BASE_URL}/documents/${documentId}/analysis`); if (!response.ok) { throw new Error(`Error fetching document analysis: ${response.statusText}`); } const data = await response.json(); // Check if analysis is complete if (data.status === 'pending' || data.status === 'processing') { alert('Document analysis is still in progress. Please try again later.'); return; } if (data.status === 'failed') { alert('Document analysis failed. Please try resubmitting the document.'); return; } // If no report is available if (!data.report) { alert('No analysis report available for this document.'); return; } // Render report renderReport(data.report); // Show report section reportSection.style.display = 'block'; // Scroll to report section reportSection.scrollIntoView({ behavior: 'smooth' }); } catch (error) { console.error('Error viewing document report:', error); alert('Failed to load document report. Please try again.'); } finally { // Hide loading overlay loadingOverlay.style.display = 'none'; } } // Handle standard file selection function handleStandardFileSelection(file) { // Check if file is JSON if (!file.name.toLowerCase().endsWith('.json')) { alert('Please select a JSON file for standards'); return; } // Update file info display standardFileName.textContent = file.name; standardFileSize.textContent = formatFileSize(file.size); // Show file info section standardUploadArea.style.display = 'none'; standardFileInfo.style.display = 'block'; } // Reset standard file input function resetStandardFileInput() { standardFileInput.value = ''; standardUploadArea.style.display = 'block'; standardFileInfo.style.display = 'none'; standardsUpload.style.display = 'none'; } // Upload standard async function uploadStandard(file) { try { // Show loading overlay loadingOverlay.style.display = 'flex'; const formData = new FormData(); formData.append('file', file); const response = await fetch(`${API_BASE_URL}/standards/upload`, { method: 'POST', body: formData }); if (!response.ok) { throw new Error(`Error uploading standard: ${response.statusText}`); } const data = await response.json(); console.log('Standard upload response:', data); // Store standard in local storage const standard = { id: data.standard_id, name: data.name, requirement_count: data.requirement_count, date: new Date().toISOString() }; saveStandard(standard); resetStandardFileInput(); loadStandards(); alert(`Standard "${data.name}" uploaded successfully with ${data.requirement_count} requirements.`); } catch (error) { console.error('Error uploading standard:', error); alert('Failed to upload standard. Please try again.'); } finally { loadingOverlay.style.display = 'none'; } } // Save standard to local storage function saveStandard(standard) { let standards = JSON.parse(localStorage.getItem(STANDARDS_STORAGE_KEY) || '[]'); // Check if standard already exists const existingIndex = standards.findIndex(std => std.id === standard.id); if (existingIndex !== -1) { // Update existing standard standards[existingIndex] = {...standards[existingIndex], ...standard}; } else { // Add new standard standards.push(standard); } // Sort standards by date (newest first) standards.sort((a, b) => new Date(b.date) - new Date(a.date)); // Save to local storage localStorage.setItem(STANDARDS_STORAGE_KEY, JSON.stringify(standards)); } // Load standards from local storage function loadStandards() { const standards = JSON.parse(localStorage.getItem(STANDARDS_STORAGE_KEY) || '[]'); // Clear standards list standardsList.innerHTML = ''; if (standards.length === 0) { noStandards.style.display = 'block'; return; } noStandards.style.display = 'none'; // Add standards to list standards.forEach(standard => { const li = createStandardListItem(standard); standardsList.appendChild(li); }); } // Create standard list item function createStandardListItem(standard) { const li = document.createElement('li'); li.className = 'standard-item'; let dateStr = 'Unknown date'; try { dateStr = new Date(standard.date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); } catch (e) { console.warn('Error formatting date:', e); } li.innerHTML = `

${standard.name || 'Unnamed standard'}

Added on ${dateStr}

${standard.requirement_count} requirements

`; // Add event listeners to buttons const viewButton = li.querySelector('.view-standard-button'); viewButton.addEventListener('click', function() { const standardId = this.getAttribute('data-id'); viewStandard(standardId); }); return li; } // View standard details async function viewStandard(standardId) { try { // Show loading overlay loadingOverlay.style.display = 'flex'; // Fetch standard details const response = await fetch(`${API_BASE_URL}/standards/${standardId}`); if (!response.ok) { throw new Error(`Error fetching standard: ${response.statusText}`); } const standard = await response.json(); // Create modal content const modalContent = `

${standard.name}

${standard.description || 'No description available'}

Requirements (${standard.requirements.length})

${standard.requirements.length === 0 ? '

No requirements defined

' : ''}
`; // Create modal const modal = document.createElement('div'); modal.className = 'modal'; modal.innerHTML = ` `; // Add modal to body document.body.appendChild(modal); // Add close button event listener modal.querySelector('.close-button').addEventListener('click', function() { document.body.removeChild(modal); }); // Close modal when clicking outside modal.addEventListener('click', function(e) { if (e.target === modal) { document.body.removeChild(modal); } }); } catch (error) { console.error('Error viewing standard:', error); alert('Failed to load standard details. Please try again.'); } finally { // Hide loading overlay loadingOverlay.style.display = 'none'; } } // Render report function renderReport(report) { // Calculate issue counts const criticalCount = report.issues.filter(issue => issue.level === 'critical').length; const majorCount = report.issues.filter(issue => issue.level === 'major').length; const minorCount = report.issues.filter(issue => issue.level === 'minor').length; const infoCount = report.issues.filter(issue => issue.level === 'info').length; // Format score as percentage const scorePercentage = (report.compliance_score * 100).toFixed(1); // Determine score color based on percentage let scoreColor = '#2ecc71'; // Default green if (scorePercentage < 50) { scoreColor = '#e74c3c'; // Red for low score } else if (scorePercentage < 80) { scoreColor = '#f39c12'; // Orange for medium score } // Create HTML let html = `
${scorePercentage}%
Compliance Score
${criticalCount}
Critical Issues
${majorCount}
Major Issues
${minorCount}
Minor Issues
${infoCount}
Info Issues

Summary

${report.summary}

${report.applied_standards && report.applied_standards.length > 0 ? `

Applied Standards

` : ''}

Compliance Issues

`; if (report.issues.length === 0) { html += '

No compliance issues found. Great job!

'; } else { // Sort issues by level (critical first) const sortedIssues = [...report.issues].sort((a, b) => { const levelOrder = { 'critical': 0, 'major': 1, 'minor': 2, 'info': 3 }; return levelOrder[a.level] - levelOrder[b.level]; }); // Add issues to HTML sortedIssues.forEach(issue => { html += `
${issue.section}
${issue.level}
${issue.description}
${issue.reasoning ? `
Reasoning: ${issue.reasoning}
` : ''} ${issue.standard_references && issue.standard_references.length > 0 ? `
Standard References:
    ${issue.standard_references.map(ref => `
  • ${ref}
  • `).join('')}
` : ''}
Recommendation: ${issue.recommendation}
`; }); } html += '
'; // Set report HTML reportContainer.innerHTML = html; } // Check document status async function checkDocumentStatus(documentId) { try { // Start with a short delay let delay = 2000; const maxAttempts = 10; for (let attempt = 0; attempt < maxAttempts; attempt++) { // Wait for the delay await new Promise(resolve => setTimeout(resolve, delay)); // Fetch document status const response = await fetch(`${API_BASE_URL}/documents/${documentId}`); if (!response.ok) { throw new Error(`Error checking document status: ${response.statusText}`); } const data = await response.json(); // Update document in local storage const documents = JSON.parse(localStorage.getItem(DOCUMENTS_STORAGE_KEY) || '[]'); const documentIndex = documents.findIndex(doc => doc.id === documentId); if (documentIndex !== -1) { documents[documentIndex].status = data.status; localStorage.setItem(DOCUMENTS_STORAGE_KEY, JSON.stringify(documents)); } // If processing is complete or failed, stop checking if (data.status === 'completed' || data.status === 'failed') { // If completed, show the report if (data.status === 'completed' && data.reports && data.reports.length > 0) { viewDocumentReport(documentId); } break; } // Increase delay for next attempt (exponential backoff) delay = Math.min(delay * 1.5, 10000); } // Refresh document list loadDocuments(); } catch (error) { console.error('Error checking document status:', error); } } // Resubmit document function resubmitDocument(documentId) { // Trigger file input for resubmission fileInput.setAttribute('data-resubmit-id', documentId); fileInput.click(); // Listen for file selection (one-time event listener) const handleResubmitFileSelection = async function() { if (fileInput.files.length) { const resubmitId = fileInput.getAttribute('data-resubmit-id'); if (resubmitId) { // Handle resubmission await handleDocumentResubmission(resubmitId, fileInput.files[0]); // Remove attribute and event listener fileInput.removeAttribute('data-resubmit-id'); fileInput.removeEventListener('change', handleResubmitFileSelection); } } }; fileInput.addEventListener('change', handleResubmitFileSelection); } // Handle document resubmission async function handleDocumentResubmission(documentId, file) { try { // Show loading overlay loadingOverlay.style.display = 'flex'; // Create form data const formData = new FormData(); formData.append('file', file); // Send request to API const response = await fetch(`${API_BASE_URL}/documents/${documentId}/resubmit`, { method: 'POST', body: formData }); if (!response.ok) { throw new Error(`Error resubmitting document: ${response.statusText}`); } const data = await response.json(); // Update document in local storage const document = { id: data.document_id, name: file.name, status: data.status, date: new Date().toISOString(), size: file.size }; saveDocument(document); // Load updated document list loadDocuments(); // Check document status and show report if ready checkDocumentStatus(data.document_id); // Show success message alert('Document resubmitted successfully! The analysis is in progress.'); } catch (error) { console.error('Error resubmitting document:', error); alert('Failed to resubmit document. Please try again.'); } finally { // Hide loading overlay loadingOverlay.style.display = 'none'; } } });