From 07d8f83883f4a7f6262edec70f68d5067f67b739 Mon Sep 17 00:00:00 2001 From: aewing Date: Sun, 28 Dec 2025 03:10:53 +0000 Subject: [PATCH] added manager info view --- frontend/index.html | 838 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 837 insertions(+), 1 deletion(-) diff --git a/frontend/index.html b/frontend/index.html index 22de787..1b8d2d6 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -159,6 +159,14 @@ + + +
+
@@ -1122,6 +1131,810 @@ newNote.innerHTML = ''; tbButtons.forEach(b=>b.classList.remove('bg-blue-100', 'text-gray-900', 'border-blue-600')); } + + async function populateManagerInfo(job) { + const managerInfoTable = document.getElementById('managerInfoTable'); + managerInfoTable.innerHTML = ''; + + // Row 1: Job Full Name (spans 2 cols), Manager, Job Status + const row1 = document.createElement('div'); + row1.className = 'grid grid-cols-4 gap-4 pb-3 border-b border-gray-200'; + + const jobFullNameDiv = document.createElement('div'); + jobFullNameDiv.className = 'col-span-2'; + jobFullNameDiv.innerHTML = `
Job Full Name
${job.Job_Full_Name || '—'}
`; + + const managerDiv = document.createElement('div'); + managerDiv.innerHTML = `
Manager
${job.Manager || '—'}
`; + + const jobStatusDiv = document.createElement('div'); + jobStatusDiv.innerHTML = `
Job Status
${job.Job_Status || '—'}
`; + + row1.appendChild(jobFullNameDiv); + row1.appendChild(managerDiv); + row1.appendChild(jobStatusDiv); + managerInfoTable.appendChild(row1); + + // Row 2: Job Number, Job Division, Job Type, Scope + const row2 = document.createElement('div'); + row2.className = 'grid grid-cols-4 gap-4 pb-3 border-b border-gray-200'; + + const jobNum = document.createElement('div'); + jobNum.innerHTML = `
Job Number
${job.Job_Number || '—'}
`; + + const jobDivision = document.createElement('div'); + jobDivision.innerHTML = `
Job Division
${job.Job_Division || '—'}
`; + + const jobType = document.createElement('div'); + jobType.innerHTML = `
Job Type
${job.Job_Type || '—'}
`; + + const scopeDiv = document.createElement('div'); + scopeDiv.innerHTML = `
Scope
`; + + row2.appendChild(jobNum); + row2.appendChild(jobDivision); + row2.appendChild(jobType); + row2.appendChild(scopeDiv); + managerInfoTable.appendChild(row2); + + // Row 3: Company/Client, Contact Person, Contact Number, EXT. + const row3 = document.createElement('div'); + row3.className = 'grid grid-cols-4 gap-4 pb-3 border-b border-gray-200'; + + const companyClient = document.createElement('div'); + companyClient.innerHTML = `
Company/Client
${job.Company_Client || '—'}
`; + + const contactPerson = document.createElement('div'); + contactPerson.innerHTML = `
Contact Person
${job.Contact_Person || '—'}
`; + + const contactNumber = document.createElement('div'); + contactNumber.innerHTML = `
Contact Number
`; + const phoneDiv = document.createElement('div'); + phoneDiv.className = 'text-center'; + phoneDiv.style.fontSize = '10px'; + if(job.Phone_Number && job.Phone_Number.trim() !== ''){ + const phoneLink = document.createElement('a'); + const phoneNumber = job.Phone_Number.replace(/\D/g, ''); + phoneLink.href = `tel:${phoneNumber}`; + phoneLink.className = 'text-blue-600 hover:text-blue-800 underline'; + phoneLink.textContent = job.Phone_Number; + phoneDiv.appendChild(phoneLink); + } else { + phoneDiv.className = 'text-gray-900 text-center'; + phoneDiv.textContent = '—'; + } + contactNumber.appendChild(phoneDiv); + + const extn = document.createElement('div'); + extn.innerHTML = `
EXT.
`; + + row3.appendChild(companyClient); + row3.appendChild(contactPerson); + row3.appendChild(contactNumber); + row3.appendChild(extn); + managerInfoTable.appendChild(row3); + + // Row 4: Job Address (spans 2 cols), Map Icons (3rd col), EST Cost (4th col) + const row4 = document.createElement('div'); + row4.className = 'grid grid-cols-4 gap-4 pb-3 border-b border-gray-200'; + + // Job Address spanning 2 columns + const addressContainer = document.createElement('div'); + addressContainer.className = 'col-span-2'; + addressContainer.innerHTML = `
Job Address
`; + const addressVal = document.createElement('div'); + addressVal.style.fontSize = '10px'; + addressVal.className = 'text-gray-900'; + addressVal.textContent = job.Job_Address || '—'; + addressContainer.appendChild(addressVal); + + // Map Icons in 3rd column + const mapIconsContainer = document.createElement('div'); + mapIconsContainer.className = 'flex items-end gap-2'; + + if(job.Job_Address && job.Job_Address.trim() !== ''){ + const appleLink = document.createElement('a'); + const encodedAddress = encodeURIComponent(job.Job_Address); + appleLink.href = `https://maps.apple.com/?q=${encodedAddress}`; + appleLink.target = '_blank'; + appleLink.className = 'inline-flex items-center justify-center w-5 h-5 hover:opacity-80 transition-opacity'; + appleLink.title = 'Open in Apple Maps'; + appleLink.innerHTML = 'Apple'; + mapIconsContainer.appendChild(appleLink); + + const googleLink = document.createElement('a'); + googleLink.href = `https://www.google.com/maps/search/?api=1&query=${encodedAddress}`; + googleLink.target = '_blank'; + googleLink.className = 'inline-flex items-center justify-center w-5 h-5 hover:opacity-80 transition-opacity'; + googleLink.title = 'Open in Google Maps'; + googleLink.innerHTML = 'Google'; + mapIconsContainer.appendChild(googleLink); + } + + // EST Cost in 4th column (centered) + const estimatedCostContainer = document.createElement('div'); + estimatedCostContainer.innerHTML = `
EST Cost
`; + + row4.appendChild(addressContainer); + row4.appendChild(mapIconsContainer); + row4.appendChild(estimatedCostContainer); + managerInfoTable.appendChild(row4); + + // Row 5: EST Start Date, Actual Start Date, EST End Date + const row5 = document.createElement('div'); + row5.className = 'grid grid-cols-3 gap-4 pb-3 border-b border-gray-200'; + + // Format the start date for both estimated and actual (short format) + let startDateFormatted = '—'; + if(job.Start_Date){ + try { + const date = new Date(job.Start_Date); + startDateFormatted = date.toLocaleDateString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit' }); + } catch(e) { + startDateFormatted = job.Start_Date; + } + } + + const estimatedStartDate = document.createElement('div'); + estimatedStartDate.innerHTML = `
EST Start Date
${startDateFormatted}
`; + + const actualStartDate = document.createElement('div'); + actualStartDate.innerHTML = `
Actual Start Date
${startDateFormatted}
`; + + const estimatedEndDate = document.createElement('div'); + estimatedEndDate.innerHTML = `
EST End Date
`; + + row5.appendChild(estimatedStartDate); + row5.appendChild(actualStartDate); + 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'; + + // Tab buttons + const notesTabs = document.createElement('div'); + notesTabs.className = 'flex gap-2 mb-2'; + + const jobNotesTabBtn = document.createElement('button'); + jobNotesTabBtn.id = 'jobNotesTab'; + jobNotesTabBtn.className = 'px-3 py-1 font-bold uppercase bg-orange-600 text-white rounded cursor-pointer'; + jobNotesTabBtn.style.fontSize = '8px'; + jobNotesTabBtn.textContent = 'Notes'; + + const managerNotesTabBtn = document.createElement('button'); + managerNotesTabBtn.id = 'managerNotesTab'; + managerNotesTabBtn.className = 'px-3 py-1 font-bold uppercase bg-gray-300 text-gray-700 rounded hover:bg-gray-400 cursor-pointer'; + managerNotesTabBtn.style.fontSize = '8px'; + managerNotesTabBtn.textContent = 'Manager Notes'; + + notesTabs.appendChild(jobNotesTabBtn); + notesTabs.appendChild(managerNotesTabBtn); + notesSection.appendChild(notesTabs); + + // Job Notes Content + const jobNotesContent = document.createElement('div'); + jobNotesContent.id = 'jobNotesContent'; + jobNotesContent.className = 'notes-tab-content'; + + // 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.style.fontSize = '10px'; + jobNotesDisplay.innerHTML = job.Notes || 'No notes available'; + jobNotesContent.appendChild(jobNotesDisplay); + + // Add Note button below notes display + const addNoteBtn = document.createElement('button'); + addNoteBtn.className = 'mb-2 px-3 py-1 bg-green-600 text-white rounded font-bold hover:bg-green-700 cursor-pointer'; + addNoteBtn.style.fontSize = '8px'; + addNoteBtn.textContent = 'Add A Note'; + jobNotesContent.appendChild(addNoteBtn); + + // Note editor (collapsed by default) + const noteEditor = document.createElement('div'); + noteEditor.id = 'jobNoteEditor'; + noteEditor.className = 'hidden mb-3 p-2 border border-gray-300 rounded bg-gray-50'; + noteEditor.innerHTML = ` +
+ + + + + + + +
+
+
+ + + +
+ `; + jobNotesContent.appendChild(noteEditor); + + // File columns - always create them + const fileColumns = document.createElement('div'); + fileColumns.id = 'jobNotesFileColumns'; + fileColumns.className = 'grid grid-cols-3 gap-4 mt-3'; + + // Initialize with empty state + fileColumns.innerHTML = ` +
+
Plans
+
Loading...
+
+
+
Estimates
+
Loading...
+
+
+
Other Files
+
Loading...
+
+ `; + jobNotesContent.appendChild(fileColumns); + + // Manager Notes Content + const managerNotesContent = document.createElement('div'); + managerNotesContent.id = 'managerNotesContent'; + managerNotesContent.className = 'notes-tab-content hidden'; + + // 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.style.fontSize = '10px'; + managerNotesDisplay.innerHTML = 'No manager notes available'; + managerNotesContent.appendChild(managerNotesDisplay); + + // Add Note button for manager below notes display + const addManagerNoteBtn = document.createElement('button'); + addManagerNoteBtn.className = 'mb-2 px-3 py-1 bg-green-600 text-white rounded font-bold hover:bg-green-700 cursor-pointer'; + addManagerNoteBtn.style.fontSize = '8px'; + addManagerNoteBtn.textContent = 'Add A Note'; + managerNotesContent.appendChild(addManagerNoteBtn); + + // Manager note editor (collapsed by default) + const managerNoteEditor = document.createElement('div'); + managerNoteEditor.id = 'managerNoteEditor'; + managerNoteEditor.className = 'hidden mb-3 p-2 border border-gray-300 rounded bg-gray-50'; + managerNoteEditor.innerHTML = ` +
+ + + + + + + +
+
+
+ + + +
+ `; + managerNotesContent.appendChild(managerNoteEditor); + + // File columns for manager notes - always create them + const managerFileColumns = document.createElement('div'); + managerFileColumns.id = 'managerNotesFileColumns'; + managerFileColumns.className = 'grid grid-cols-3 gap-4 mt-3'; + + // Initialize with empty state + managerFileColumns.innerHTML = ` +
+
Plans
+
Loading...
+
+
+
Estimates
+
Loading...
+
+
+
Other Files
+
Loading...
+
+ `; + managerNotesContent.appendChild(managerFileColumns); + + notesSection.appendChild(jobNotesContent); + notesSection.appendChild(managerNotesContent); + managerInfoTable.appendChild(notesSection); + + // Tab switching event listeners + jobNotesTabBtn.addEventListener('click', (e) => { + console.log('Notes tab clicked'); + e.preventDefault(); + e.stopPropagation(); + jobNotesTabBtn.className = 'px-3 py-1 text-xs font-bold uppercase bg-orange-600 text-white rounded cursor-pointer'; + managerNotesTabBtn.className = 'px-3 py-1 text-xs font-bold uppercase bg-gray-300 text-gray-700 rounded hover:bg-gray-400 cursor-pointer'; + jobNotesContent.classList.remove('hidden'); + managerNotesContent.classList.add('hidden'); + }); + + managerNotesTabBtn.addEventListener('click', (e) => { + console.log('Manager Notes tab clicked'); + e.preventDefault(); + e.stopPropagation(); + managerNotesTabBtn.className = 'px-3 py-1 text-xs font-bold uppercase bg-orange-600 text-white rounded cursor-pointer'; + jobNotesTabBtn.className = 'px-3 py-1 text-xs font-bold uppercase bg-gray-300 text-gray-700 rounded hover:bg-gray-400 cursor-pointer'; + managerNotesContent.classList.remove('hidden'); + jobNotesContent.classList.add('hidden'); + }); + + // Note editor controls + addNoteBtn.addEventListener('click', (e) => { + console.log('Add Note clicked'); + e.preventDefault(); + addNoteBtn.classList.add('hidden'); + noteEditor.classList.remove('hidden'); + }); + + addManagerNoteBtn.addEventListener('click', (e) => { + console.log('Add Manager Note clicked'); + e.preventDefault(); + addManagerNoteBtn.classList.add('hidden'); + managerNoteEditor.classList.remove('hidden'); + }); + + // Populate file columns + if(job.Job_Folder_Link && String(job.Job_Folder_Link).trim() !== ''){ + console.log('Fetching files for notes sections...'); + loadFilesForNotes(job.Job_Folder_Link).then(files => { + console.log('Files fetched, populating columns:', files.length); + populateNotesFileColumns(files, 'jobNotesFileColumns'); + populateNotesFileColumns(files, 'managerNotesFileColumns'); + }).catch(err => { + console.log('Error loading files for notes:', err); + // Show empty state on error + populateNotesFileColumns([], 'jobNotesFileColumns'); + populateNotesFileColumns([], 'managerNotesFileColumns'); + }); + } else { + console.log('No Job_Folder_Link, showing empty state'); + // Show empty state when no folder link + populateNotesFileColumns([], 'jobNotesFileColumns'); + populateNotesFileColumns([], 'managerNotesFileColumns'); + } + + // Setup toolbar functionality for job notes editor + setupNoteEditorToolbar('jobNewNote', 'jobNoteEditor'); + + // Setup toolbar functionality for manager notes editor + setupNoteEditorToolbar('managerNewNote', 'managerNoteEditor'); + + // Setup close functionality + document.getElementById('jobNoteCollapse').addEventListener('click', (e) => { + console.log('Close job note editor'); + e.preventDefault(); + noteEditor.classList.add('hidden'); + addNoteBtn.classList.remove('hidden'); + }); + + document.getElementById('managerNoteCollapse').addEventListener('click', (e) => { + console.log('Close manager note editor'); + e.preventDefault(); + managerNoteEditor.classList.add('hidden'); + addManagerNoteBtn.classList.remove('hidden'); + }); + + // Setup submit functionality + document.getElementById('jobNoteSubmit').addEventListener('click', async(e) => { + e.preventDefault(); + await submitNote('jobNewNote', job, false); + noteEditor.classList.add('hidden'); + addNoteBtn.classList.remove('hidden'); + }); + + document.getElementById('jobNoteClear').addEventListener('click', (e) => { + e.preventDefault(); + document.getElementById('jobNewNote').innerHTML = ''; + }); + + document.getElementById('managerNoteSubmit').addEventListener('click', async(e) => { + e.preventDefault(); + await submitNote('managerNewNote', job, true); + managerNoteEditor.classList.add('hidden'); + addManagerNoteBtn.classList.remove('hidden'); + }); + + document.getElementById('managerNoteClear').addEventListener('click', (e) => { + e.preventDefault(); + document.getElementById('managerNewNote').innerHTML = ''; + }); + } + + // Load files for notes sections + async function loadFilesForNotes(folderLink) { + console.log('loadFilesForNotes called with:', folderLink); + + const graphToken = await ensureGraphToken(); + const headers = graphToken ? { 'x-graph-token': graphToken } : {}; + + try { + const resp = await fetch(`/api/job-files?link=${encodeURIComponent(folderLink)}`, { headers }); + + if(!resp.ok) { + console.error('Failed to fetch files:', resp.status, resp.statusText); + return []; + } + + const data = await resp.json(); + console.log('Files loaded:', data.items?.length || 0); + return data.items || []; + } catch(err) { + console.error('Error in loadFilesForNotes:', err); + return []; + } + } + + // Setup toolbar for note editors + function setupNoteEditorToolbar(editorId, containerId) { + const editor = document.getElementById(editorId); + const container = document.getElementById(containerId); + if(!editor || !container) return; + + let savedSelection = null; + function saveSelection() { + const sel = window.getSelection(); + if(sel.rangeCount > 0) savedSelection = sel.getRangeAt(0); + } + function restoreSelection() { + const sel = window.getSelection(); + if(savedSelection) { + sel.removeAllRanges(); + sel.addRange(savedSelection); + } + } + + editor.addEventListener('mouseup', saveSelection); + editor.addEventListener('keyup', saveSelection); + editor.addEventListener('focus', saveSelection); + + const buttons = container.querySelectorAll('[data-cmd]'); + buttons.forEach(btn => { + btn.addEventListener('mousedown', e => { + e.preventDefault(); + restoreSelection(); + const cmd = btn.dataset.cmd; + if(!cmd) return; + + if(cmd === 'foreColor') { + const val = btn.dataset.val; + document.execCommand(cmd, false, val); + buttons.forEach(b => { + if(b.dataset.cmd === 'foreColor' && b !== btn) { + b.classList.remove('bg-blue-100', 'text-gray-900', 'border-blue-600'); + } + }); + btn.classList.toggle('bg-blue-100'); + btn.classList.toggle('text-gray-900'); + btn.classList.toggle('border-blue-600'); + } else if(cmd === 'removeFormat') { + restoreSelection(); + const sel = window.getSelection(); + if(sel.rangeCount > 0) { + const range = sel.getRangeAt(0); + let content = range.toString(); + range.deleteContents(); + range.insertNode(document.createTextNode(content)); + range.setStartAfter(range.startContainer); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); + } + buttons.forEach(b => b.classList.remove('bg-blue-100', 'text-gray-900', 'border-blue-600')); + } else { + document.execCommand(cmd, false, null); + btn.classList.toggle('bg-blue-100'); + btn.classList.toggle('text-gray-900'); + btn.classList.toggle('border-blue-600'); + } + + editor.focus(); + saveSelection(); + }); + }); + } + + // Submit note function + async function submitNote(editorId, job, isManagerNote) { + const editor = document.getElementById(editorId); + const html = editor.innerHTML.trim(); + if(!html) { + alert('Enter note text'); + return; + } + + const now = new Date(); + const timestamp = now.toLocaleString('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + hour12: true + }); + const currentUserName = getDisplayName(); + const noteType = isManagerNote ? '[MANAGER NOTE]' : ''; + const formatted = `${noteType}[${currentUserName} - ${timestamp}] [Job #${job?.Job_Number||'N/A'}] ${html}\n\n`; + const updatedNotes = formatted + (job.Notes || ''); + + try { + const payloadNote = { + Note: formatted.trim(), + Job_Number: job?.Job_Number || '', + Job_Number_Id: job?.id ? [job.id] : [] + }; + + // Update job record + const resJob = await fetch(`${PB_COLLECTION_URL}/${job.id}`, { + method: 'PATCH', + headers: {'Content-Type': 'application/json', ...authHeaders()}, + body: JSON.stringify({Notes: updatedNotes}) + }); + + if(!resJob.ok) throw new Error('Job_Info update failed: ' + resJob.status); + const updated = await resJob.json(); + const jobIdToLink = updated.id || job.id; + + // Create note in notes collection + payloadNote.Job_Number_Id = [jobIdToLink]; + const resNote = await fetch(`${NOTES_COLLECTION_URL}`, { + method: 'POST', + headers: {'Content-Type': 'application/json', ...authHeaders()}, + body: JSON.stringify(payloadNote) + }); + + if(!resNote.ok) { + console.warn('Note creation failed with status', resNote.status); + } + + // Update UI + currentJob.Notes = updatedNotes; + editor.innerHTML = ''; + alert('Note saved successfully!'); + + // Refresh the display + if(currentJob) { + populateManagerInfo(currentJob); + } + } catch(err) { + console.error('Error saving note:', err); + alert('Failed to save note: ' + err.message); + } + } + + // Function to populate file columns in notes section + function populateNotesFileColumns(files, containerId) { + const container = document.getElementById(containerId); + if(!container) { + console.log('Container not found:', containerId); + return; + } + + console.log('Populating', containerId, 'with', files.length, 'files'); + container.innerHTML = ''; + + // Categorize files + const plans = []; + const estimates = []; + const otherFiles = []; + + if(files && files.length > 0) { + 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 { + otherFiles.push(file); + } + }); + } + + // 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 = '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); + + const fileName = document.createElement('span'); + fileName.textContent = file.name; + fileName.className = 'truncate'; + fileItem.appendChild(fileName); + + 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 = '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); + + const fileName = document.createElement('span'); + fileName.textContent = file.name; + fileName.className = 'truncate'; + fileItem.appendChild(fileName); + + fileItem.onclick = () => openFilePreview(file); + estimatesList.appendChild(fileItem); + }); + } else { + estimatesList.innerHTML = `
No estimates
`; + } + estimatesCol.appendChild(estimatesList); + + // Other Files column + const otherCol = document.createElement('div'); + otherCol.innerHTML = `
Other Files
`; + const otherList = document.createElement('div'); + otherList.className = 'space-y-1'; + if(otherFiles.length > 0) { + otherFiles.forEach(file => { + const fileItem = document.createElement('div'); + 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); + + const fileName = document.createElement('span'); + fileName.textContent = file.name; + fileName.className = 'truncate'; + fileItem.appendChild(fileName); + + fileItem.onclick = () => openFilePreview(file); + otherList.appendChild(fileItem); + }); + } else { + otherList.innerHTML = `
No other files
`; + } + otherCol.appendChild(otherList); + + container.appendChild(plansCol); + container.appendChild(estimatesCol); + container.appendChild(otherCol); + } + const closeModal = ()=>{ detailModal.classList.add('hidden'); renderCards(visibleJobs); }; document.getElementById('btnClose').addEventListener('click',closeModal); document.getElementById('backBtn').addEventListener('click',closeModal); @@ -1130,30 +1943,53 @@ function switchTab(tab){ const infoTab = document.getElementById('infoTab'); const notesTab = document.getElementById('notesTab'); + const managerInfoTab = document.getElementById('managerInfoTab'); const infoContent = document.getElementById('infoContent'); const notesContent = document.getElementById('notesContent'); + const managerInfoContent = document.getElementById('managerInfoContent'); const infoHeading = document.getElementById('infoHeading'); const notesHeading = document.getElementById('notesHeading'); + const managerInfoHeading = document.getElementById('managerInfoHeading'); if(tab === 'info'){ infoTab.className = 'px-4 py-2 text-sm font-medium text-blue-600 border-b-2 border-blue-600 -mb-px'; notesTab.className = 'px-4 py-2 text-sm font-medium text-gray-500 hover:text-gray-700'; + managerInfoTab.className = 'px-4 py-2 text-sm font-medium text-gray-500 hover:text-gray-700'; infoContent.classList.remove('hidden'); notesContent.classList.add('hidden'); + managerInfoContent.classList.add('hidden'); if(infoHeading) infoHeading.classList.remove('hidden'); if(notesHeading) notesHeading.classList.add('hidden'); - } else { + if(managerInfoHeading) managerInfoHeading.classList.add('hidden'); + } else if(tab === 'notes') { infoTab.className = 'px-4 py-2 text-sm font-medium text-gray-500 hover:text-gray-700'; notesTab.className = 'px-4 py-2 text-sm font-medium text-orange-600 border-b-2 border-orange-600 -mb-px'; + managerInfoTab.className = 'px-4 py-2 text-sm font-medium text-gray-500 hover:text-gray-700'; infoContent.classList.add('hidden'); notesContent.classList.remove('hidden'); + managerInfoContent.classList.add('hidden'); if(infoHeading) infoHeading.classList.add('hidden'); if(notesHeading) notesHeading.classList.remove('hidden'); + if(managerInfoHeading) managerInfoHeading.classList.add('hidden'); + } else if(tab === 'managerInfo') { + infoTab.className = 'px-4 py-2 text-sm font-medium text-gray-500 hover:text-gray-700'; + notesTab.className = 'px-4 py-2 text-sm font-medium text-gray-500 hover:text-gray-700'; + managerInfoTab.className = 'px-4 py-2 text-sm font-medium text-green-600 border-b-2 border-green-600 -mb-px'; + infoContent.classList.add('hidden'); + notesContent.classList.add('hidden'); + managerInfoContent.classList.remove('hidden'); + if(infoHeading) infoHeading.classList.add('hidden'); + if(notesHeading) notesHeading.classList.add('hidden'); + if(managerInfoHeading) managerInfoHeading.classList.remove('hidden'); } } document.getElementById('infoTab').addEventListener('click',()=>switchTab('info')); document.getElementById('notesTab').addEventListener('click',()=>switchTab('notes')); + document.getElementById('managerInfoTab').addEventListener('click',()=>{ + switchTab('managerInfo'); + populateManagerInfo(currentJob); + }); // --- Toolbar --- let savedSelection = null;