diff --git a/frontend/index.html b/frontend/index.html index 92a1978..b2e00af 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -698,6 +698,12 @@ // Remove trailing punctuation (period, comma, etc.) transcript = transcript.replace(/[.,!?;:]+$/, ''); + // Remove trailing "job" if present + transcript = transcript.replace(/\s+job$/i, ''); + + // Replace "new" with "latest" + transcript = transcript.replace(/\bnew\b/gi, 'latest'); + // Capitalize each word (title case) transcript = transcript.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() @@ -721,6 +727,16 @@ // Trigger search with results applyFiltersAndRender(); + // If exactly 1 result, automatically open Manager Info + if (visibleJobs.length === 1) { + const job = visibleJobs[0]; + currentJob = job; + detailModal.classList.remove('hidden'); + document.getElementById('detailBox').scrollTop = 0; + switchTab('managerInfo'); + populateManagerInfo(job); + } + // Check if all results are final, if so prepare to stop const allFinal = Array.from(event.results).every(r => r.isFinal); if (allFinal && event.results.length > 0) { @@ -770,6 +786,12 @@ return; } + // Check if text is highlighted and clear it if so + if (searchBox.selectionStart === 0 && searchBox.selectionEnd === searchBox.value.length && searchBox.value.length > 0) { + searchBox.value = ''; + baseSearchText = ''; + } + // Request permission first time if (!hasPermission) { const granted = await requestMicPermission(); @@ -1314,102 +1336,6 @@ row5.appendChild(estimatedEndDate); managerInfoTable.appendChild(row5); - // Links Section: Plans, Estimates, Documents & Files - if(job.Job_Folder_Link && String(job.Job_Folder_Link).trim() !== ''){ - try { - const files = await fetchJobFiles(job); - - // Categorize files - const plans = []; - const estimates = []; - const documentsAndFiles = []; - - files.forEach(file => { - const name = file.name.toLowerCase(); - if(name.includes('plan') || name.includes('drawing') || name.includes('dwg')) { - plans.push(file); - } else if(name.includes('estimate') || name.includes('est')) { - estimates.push(file); - } else { - documentsAndFiles.push(file); - } - }); - - // Links Section Container - const linksSection = document.createElement('div'); - linksSection.className = 'mt-4 pb-3 border-b border-gray-200'; - linksSection.innerHTML = `
Links
`; - - const linksGrid = document.createElement('div'); - linksGrid.className = 'grid grid-cols-3 gap-4'; - - // Plans column - const plansCol = document.createElement('div'); - plansCol.innerHTML = `
Plans
`; - const plansList = document.createElement('div'); - plansList.className = 'space-y-1'; - if(plans.length > 0) { - plans.forEach(file => { - const fileItem = document.createElement('div'); - fileItem.className = 'text-blue-600 hover:text-blue-800 cursor-pointer underline'; - fileItem.style.fontSize = '10px'; - fileItem.textContent = file.name; - fileItem.onclick = () => openFilePreview(file); - plansList.appendChild(fileItem); - }); - } else { - plansList.innerHTML = `
No plans
`; - } - plansCol.appendChild(plansList); - - // Estimates column - const estimatesCol = document.createElement('div'); - estimatesCol.innerHTML = `
Estimates
`; - const estimatesList = document.createElement('div'); - estimatesList.className = 'space-y-1'; - if(estimates.length > 0) { - estimates.forEach(file => { - const fileItem = document.createElement('div'); - fileItem.className = 'text-blue-600 hover:text-blue-800 cursor-pointer underline'; - fileItem.style.fontSize = '10px'; - fileItem.textContent = file.name; - fileItem.onclick = () => openFilePreview(file); - estimatesList.appendChild(fileItem); - }); - } else { - estimatesList.innerHTML = `
No estimates
`; - } - estimatesCol.appendChild(estimatesList); - - // Documents & Files column - const docsCol = document.createElement('div'); - docsCol.innerHTML = `
Documents & Files
`; - const docsList = document.createElement('div'); - docsList.className = 'space-y-1'; - if(documentsAndFiles.length > 0) { - documentsAndFiles.forEach(file => { - const fileItem = document.createElement('div'); - fileItem.className = 'text-blue-600 hover:text-blue-800 cursor-pointer underline'; - fileItem.style.fontSize = '10px'; - fileItem.textContent = file.name; - fileItem.onclick = () => openFilePreview(file); - docsList.appendChild(fileItem); - }); - } else { - docsList.innerHTML = `
No documents
`; - } - docsCol.appendChild(docsList); - - linksGrid.appendChild(plansCol); - linksGrid.appendChild(estimatesCol); - linksGrid.appendChild(docsCol); - linksSection.appendChild(linksGrid); - managerInfoTable.appendChild(linksSection); - } catch(err) { - console.log('Error loading files for Manager Info:', err); - } - } - // Notes Section with Tabs const notesSection = document.createElement('div'); notesSection.className = 'mt-4'; @@ -1439,12 +1365,21 @@ jobNotesContent.id = 'jobNotesContent'; jobNotesContent.className = 'notes-tab-content'; + // Container for display with expand button + const jobNotesDisplayContainer = document.createElement('div'); + jobNotesDisplayContainer.className = 'relative mb-3'; + // Display notes first const jobNotesDisplay = document.createElement('div'); - jobNotesDisplay.className = 'text-gray-600 whitespace-pre-wrap break-words p-2 border border-gray-300 rounded bg-gray-50 min-h-[80px] mb-3'; + jobNotesDisplay.id = 'jobNotesDisplay'; + jobNotesDisplay.className = 'text-gray-600 whitespace-pre-wrap break-words p-2 border border-gray-300 rounded bg-gray-50 overflow-y-auto'; jobNotesDisplay.style.fontSize = '10px'; + jobNotesDisplay.style.height = '120px'; + jobNotesDisplay.style.maxHeight = '120px'; jobNotesDisplay.innerHTML = job.Notes || 'No notes available'; - jobNotesContent.appendChild(jobNotesDisplay); + jobNotesDisplayContainer.appendChild(jobNotesDisplay); + + jobNotesContent.appendChild(jobNotesDisplayContainer); // Add Note button below notes display const addNoteBtn = document.createElement('button'); @@ -1509,12 +1444,21 @@ managerNotesContent.id = 'managerNotesContent'; managerNotesContent.className = 'notes-tab-content hidden'; + // Container for display with expand button + const managerNotesDisplayContainer = document.createElement('div'); + managerNotesDisplayContainer.className = 'relative mb-3'; + // Display manager notes first const managerNotesDisplay = document.createElement('div'); - managerNotesDisplay.className = 'text-gray-600 whitespace-pre-wrap break-words p-2 border border-gray-300 rounded bg-gray-50 min-h-[80px] mb-3'; + managerNotesDisplay.id = 'managerNotesDisplay'; + managerNotesDisplay.className = 'text-gray-600 whitespace-pre-wrap break-words p-2 border border-gray-300 rounded bg-gray-50 overflow-y-auto'; managerNotesDisplay.style.fontSize = '10px'; + managerNotesDisplay.style.height = '120px'; + managerNotesDisplay.style.maxHeight = '120px'; managerNotesDisplay.innerHTML = 'No manager notes available'; - managerNotesContent.appendChild(managerNotesDisplay); + managerNotesDisplayContainer.appendChild(managerNotesDisplay); + + managerNotesContent.appendChild(managerNotesDisplayContainer); // Add Note button for manager below notes display const addManagerNoteBtn = document.createElement('button'); @@ -1899,7 +1843,7 @@ }); const currentUserName = getDisplayName(); const noteType = isManagerNote ? 'manager' : 'job'; - const typeLabel = isManagerNote ? '[MANAGER NOTE]' : ''; + const typeLabel = isManagerNote ? '[MANAGER]' : ''; const formatted = `${typeLabel}[${currentUserName} - ${timestamp}] ${html}`; try { @@ -2008,8 +1952,8 @@ const jobNotes = allNotes.filter(n => !n.type || n.type === 'job'); const managerNotes = allNotes.filter(n => n.type === 'manager'); - // Update job notes display - const jobNotesDisplay = document.querySelector('#jobNotesContent > div:first-child'); + // Update job notes display using ID selector + const jobNotesDisplay = document.getElementById('jobNotesDisplay'); if(jobNotesDisplay) { if(jobNotes.length > 0) { jobNotesDisplay.innerHTML = jobNotes.map(n => n.Note).join('\n\n'); @@ -2018,8 +1962,8 @@ } } - // Update manager notes display - const managerNotesDisplay = document.querySelector('#managerNotesContent > div:first-child'); + // Update manager notes display using ID selector + const managerNotesDisplay = document.getElementById('managerNotesDisplay'); if(managerNotesDisplay) { if(managerNotes.length > 0) { managerNotesDisplay.innerHTML = managerNotes.map(n => n.Note).join('\n\n'); @@ -2061,6 +2005,7 @@ otherFiles.push(file); } }); + console.log('Categorized - Plans:', plans.length, 'Estimates:', estimates.length, 'Other:', otherFiles.length); } // Plans column @@ -2074,10 +2019,11 @@ fileItem.className = 'flex items-center gap-1 text-blue-600 hover:text-blue-800 cursor-pointer'; fileItem.style.fontSize = '10px'; - // File icon - const icon = document.createElement('span'); - icon.textContent = '📄'; - fileItem.appendChild(icon); + // File icon using getFileIcon + const iconWrapper = document.createElement('span'); + iconWrapper.className = 'flex-shrink-0'; + iconWrapper.innerHTML = getFileIcon(file.name, file.contentType); + fileItem.appendChild(iconWrapper); const fileName = document.createElement('span'); fileName.textContent = file.name; @@ -2103,10 +2049,11 @@ fileItem.className = 'flex items-center gap-1 text-blue-600 hover:text-blue-800 cursor-pointer'; fileItem.style.fontSize = '10px'; - // File icon - const icon = document.createElement('span'); - icon.textContent = '📊'; - fileItem.appendChild(icon); + // File icon using getFileIcon + const iconWrapper = document.createElement('span'); + iconWrapper.className = 'flex-shrink-0'; + iconWrapper.innerHTML = getFileIcon(file.name, file.contentType); + fileItem.appendChild(iconWrapper); const fileName = document.createElement('span'); fileName.textContent = file.name; @@ -2132,19 +2079,11 @@ fileItem.className = 'flex items-center gap-1 text-blue-600 hover:text-blue-800 cursor-pointer'; fileItem.style.fontSize = '10px'; - // File icon based on extension - const ext = file.name.split('.').pop().toLowerCase(); - const icon = document.createElement('span'); - if(['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext)) { - icon.textContent = '🖼️'; - } else if(['pdf'].includes(ext)) { - icon.textContent = '📕'; - } else if(['doc', 'docx'].includes(ext)) { - icon.textContent = '📝'; - } else { - icon.textContent = '📄'; - } - fileItem.appendChild(icon); + // File icon using getFileIcon + const iconWrapper = document.createElement('span'); + iconWrapper.className = 'flex-shrink-0'; + iconWrapper.innerHTML = getFileIcon(file.name, file.contentType); + fileItem.appendChild(iconWrapper); const fileName = document.createElement('span'); fileName.textContent = file.name; @@ -2410,28 +2349,28 @@ const type = (contentType || '').toLowerCase(); if (ext === 'pdf' || type.includes('pdf')) - return 'PDF'; + return 'PDF'; // Show Word icon with PDF badge to indicate it will be converted if (['doc', 'docx'].includes(ext) || type.includes('word')) - return 'W'; + return 'W'; if (['xls', 'xlsx'].includes(ext) || type.includes('excel') || type.includes('spreadsheet')) - return 'X'; + return 'X'; if (['ppt', 'pptx'].includes(ext) || type.includes('powerpoint') || type.includes('presentation')) - return 'P'; + return 'P'; if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg'].includes(ext) || type.includes('image')) - return ''; + return ''; if (['zip', 'rar', '7z', 'tar', 'gz'].includes(ext) || type.includes('zip') || type.includes('compressed')) - return ''; + return ''; if (['txt', 'csv', 'log'].includes(ext) || type.includes('text')) - return ''; + return ''; - return ''; + return ''; }; const canPreviewInline = (name, contentType) => { @@ -3069,6 +3008,17 @@ const prefs = await loadUserPreferences(); console.log('Init: loaded prefs=', prefs); applyPrefsToFilters(prefs); + + // Proactively refresh Graph token on load + console.log('Init: refreshing Graph token'); + await refreshGraphToken(); + + // Set up background token refresh every 50 minutes (tokens typically expire after 60 min) + setInterval(async () => { + console.log('Background: refreshing Graph token'); + await refreshGraphToken(); + }, 50 * 60 * 1000); // 50 minutes + console.log('Init: about to fetch jobs'); fetchAllJobs(); })();