added manager info view
This commit is contained in:
+837
-1
@@ -159,6 +159,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- MANAGER INFO Tab Content -->
|
||||||
|
<div id="managerInfoContent" class="hidden flex-1 overflow-hidden flex flex-col min-h-0">
|
||||||
|
<div id="managerInfoHeading" class="bg-green-600 text-white text-center py-2 text-sm font-semibold rounded-t-lg flex-shrink-0">MANAGER INFO</div>
|
||||||
|
<div class="px-3 pt-2 pb-1 flex-1 overflow-auto">
|
||||||
|
<div id="managerInfoTable" class="w-full space-y-3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Tab Bar at Bottom -->
|
<!-- Tab Bar at Bottom -->
|
||||||
<div class="flex items-center border-t border-gray-200 pt-2 px-3 pb-2 flex-shrink-0 bg-white rounded-b-lg">
|
<div class="flex items-center border-t border-gray-200 pt-2 px-3 pb-2 flex-shrink-0 bg-white rounded-b-lg">
|
||||||
<button id="backBtn" class="mr-6 pr-6 border-r border-gray-200 px-3 py-2 text-sm font-medium text-gray-600 hover:text-gray-800 flex items-center">
|
<button id="backBtn" class="mr-6 pr-6 border-r border-gray-200 px-3 py-2 text-sm font-medium text-gray-600 hover:text-gray-800 flex items-center">
|
||||||
@@ -169,6 +177,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button id="infoTab" class="px-4 py-2 text-sm font-medium text-blue-600 border-b-2 border-blue-600 -mb-px">INFO</button>
|
<button id="infoTab" class="px-4 py-2 text-sm font-medium text-blue-600 border-b-2 border-blue-600 -mb-px">INFO</button>
|
||||||
<button id="notesTab" class="px-4 py-2 text-sm font-medium text-gray-500 hover:text-gray-700">NOTES</button>
|
<button id="notesTab" class="px-4 py-2 text-sm font-medium text-gray-500 hover:text-gray-700">NOTES</button>
|
||||||
|
<button id="managerInfoTab" class="px-4 py-2 text-sm font-medium text-gray-500 hover:text-gray-700">MANAGER INFO</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1122,6 +1131,810 @@
|
|||||||
newNote.innerHTML = '';
|
newNote.innerHTML = '';
|
||||||
tbButtons.forEach(b=>b.classList.remove('bg-blue-100', 'text-gray-900', 'border-blue-600'));
|
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 = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1" style="font-size: 8px;">Job Full Name</div><div class="text-gray-900 truncate" style="font-size: 10px;" title="${job.Job_Full_Name || '—'}">${job.Job_Full_Name || '—'}</div>`;
|
||||||
|
|
||||||
|
const managerDiv = document.createElement('div');
|
||||||
|
managerDiv.innerHTML = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1" style="font-size: 8px;">Manager</div><div class="text-gray-900" style="font-size: 10px;">${job.Manager || '—'}</div>`;
|
||||||
|
|
||||||
|
const jobStatusDiv = document.createElement('div');
|
||||||
|
jobStatusDiv.innerHTML = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1" style="font-size: 8px;">Job Status</div><div class="text-gray-900" style="font-size: 10px;">${job.Job_Status || '—'}</div>`;
|
||||||
|
|
||||||
|
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 = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1 text-center" style="font-size: 8px;">Job Number</div><div class="text-gray-900 text-center" style="font-size: 10px;">${job.Job_Number || '—'}</div>`;
|
||||||
|
|
||||||
|
const jobDivision = document.createElement('div');
|
||||||
|
jobDivision.innerHTML = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1 text-center" style="font-size: 8px;">Job Division</div><div class="text-gray-900 text-center" style="font-size: 10px;">${job.Job_Division || '—'}</div>`;
|
||||||
|
|
||||||
|
const jobType = document.createElement('div');
|
||||||
|
jobType.innerHTML = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1 text-center" style="font-size: 8px;">Job Type</div><div class="text-gray-900 text-center" style="font-size: 10px;">${job.Job_Type || '—'}</div>`;
|
||||||
|
|
||||||
|
const scopeDiv = document.createElement('div');
|
||||||
|
scopeDiv.innerHTML = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1 text-center" style="font-size: 8px;">Scope</div><div class="text-gray-900 text-center" style="font-size: 10px;">—</div>`;
|
||||||
|
|
||||||
|
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 = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1 text-center" style="font-size: 8px;">Company/Client</div><div class="text-gray-900 text-center" style="font-size: 10px;">${job.Company_Client || '—'}</div>`;
|
||||||
|
|
||||||
|
const contactPerson = document.createElement('div');
|
||||||
|
contactPerson.innerHTML = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1 text-center" style="font-size: 8px;">Contact Person</div><div class="text-gray-900 text-center" style="font-size: 10px;">${job.Contact_Person || '—'}</div>`;
|
||||||
|
|
||||||
|
const contactNumber = document.createElement('div');
|
||||||
|
contactNumber.innerHTML = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1 text-center" style="font-size: 8px;">Contact Number</div>`;
|
||||||
|
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 = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1 text-center" style="font-size: 8px;">EXT.</div><div class="text-gray-900 text-center" style="font-size: 10px;">—</div>`;
|
||||||
|
|
||||||
|
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 = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1" style="font-size: 8px;">Job Address</div>`;
|
||||||
|
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 = '<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" style="fill: #000000;"><title>Apple</title><path d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"/></svg>';
|
||||||
|
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 = '<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" style="fill: #4285F4;"><title>Google</title><path d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"/></svg>';
|
||||||
|
mapIconsContainer.appendChild(googleLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
// EST Cost in 4th column (centered)
|
||||||
|
const estimatedCostContainer = document.createElement('div');
|
||||||
|
estimatedCostContainer.innerHTML = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1 text-center" style="font-size: 8px;">EST Cost</div><div class="text-gray-900 text-center" style="font-size: 10px;">—</div>`;
|
||||||
|
|
||||||
|
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 = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1 text-center" style="font-size: 8px;">EST Start Date</div><div class="text-gray-900 text-center" style="font-size: 10px;">${startDateFormatted}</div>`;
|
||||||
|
|
||||||
|
const actualStartDate = document.createElement('div');
|
||||||
|
actualStartDate.innerHTML = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1 text-center" style="font-size: 8px;">Actual Start Date</div><div class="text-gray-900 text-center" style="font-size: 10px;">${startDateFormatted}</div>`;
|
||||||
|
|
||||||
|
const estimatedEndDate = document.createElement('div');
|
||||||
|
estimatedEndDate.innerHTML = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1 text-center" style="font-size: 8px;">EST End Date</div><div class="text-gray-900 text-center" style="font-size: 10px;">—</div>`;
|
||||||
|
|
||||||
|
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 = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-2" style="font-size: 8px;">Links</div>`;
|
||||||
|
|
||||||
|
const linksGrid = document.createElement('div');
|
||||||
|
linksGrid.className = 'grid grid-cols-3 gap-4';
|
||||||
|
|
||||||
|
// Plans column
|
||||||
|
const plansCol = document.createElement('div');
|
||||||
|
plansCol.innerHTML = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1" style="font-size: 8px;">Plans</div>`;
|
||||||
|
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 = `<div class="text-gray-400 italic" style="font-size: 10px;">No plans</div>`;
|
||||||
|
}
|
||||||
|
plansCol.appendChild(plansList);
|
||||||
|
|
||||||
|
// Estimates column
|
||||||
|
const estimatesCol = document.createElement('div');
|
||||||
|
estimatesCol.innerHTML = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1" style="font-size: 8px;">Estimates</div>`;
|
||||||
|
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 = `<div class="text-gray-400 italic" style="font-size: 10px;">No estimates</div>`;
|
||||||
|
}
|
||||||
|
estimatesCol.appendChild(estimatesList);
|
||||||
|
|
||||||
|
// Documents & Files column
|
||||||
|
const docsCol = document.createElement('div');
|
||||||
|
docsCol.innerHTML = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1" style="font-size: 8px;">Documents & Files</div>`;
|
||||||
|
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 = `<div class="text-gray-400 italic" style="font-size: 10px;">No documents</div>`;
|
||||||
|
}
|
||||||
|
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 || '<span class="text-gray-400 italic">No notes available</span>';
|
||||||
|
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 = `
|
||||||
|
<div class="flex flex-wrap gap-1 mb-2">
|
||||||
|
<button data-cmd="bold" class="h-6 w-8 rounded border border-gray-400 bg-gray-400 text-gray-900 cursor-pointer flex items-center justify-center" style="font-size: 8px;"><strong>B</strong></button>
|
||||||
|
<button data-cmd="italic" class="h-6 w-8 rounded border border-gray-400 bg-gray-400 text-gray-900 cursor-pointer flex items-center justify-center" style="font-size: 8px;"><em>I</em></button>
|
||||||
|
<button data-cmd="underline" class="h-6 w-8 rounded border border-gray-400 bg-gray-400 text-gray-900 cursor-pointer flex items-center justify-center" style="font-size: 8px;"><u>U</u></button>
|
||||||
|
<button data-cmd="foreColor" data-val="#ff0000" class="h-6 w-8 rounded border border-gray-400 bg-gray-400 text-gray-900 cursor-pointer flex items-center justify-center text-red-500" style="font-size: 8px;">A</button>
|
||||||
|
<button data-cmd="foreColor" data-val="#0000ff" class="h-6 w-8 rounded border border-gray-400 bg-gray-400 text-gray-900 cursor-pointer flex items-center justify-center text-blue-600" style="font-size: 8px;">A</button>
|
||||||
|
<button data-cmd="foreColor" data-val="#000000" class="h-6 w-8 rounded border border-gray-400 bg-gray-400 text-gray-900 cursor-pointer flex items-center justify-center text-black" style="font-size: 8px;">A</button>
|
||||||
|
<button data-cmd="removeFormat" class="h-6 px-2 rounded border border-gray-400 bg-gray-400 text-white cursor-pointer flex items-center justify-center" style="font-size: 8px;">Reset</button>
|
||||||
|
</div>
|
||||||
|
<div id="jobNewNote" contenteditable="true" class="min-h-14 max-h-[120px] overflow-auto p-2 border border-gray-600 rounded text-sm bg-white mb-2"></div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button id="jobNoteSubmit" class="px-3 py-1 rounded text-white bg-green-600 hover:bg-green-700 font-bold cursor-pointer" style="font-size: 8px;">Submit</button>
|
||||||
|
<button id="jobNoteClear" class="px-3 py-1 rounded text-white bg-gray-500 hover:bg-gray-600 font-bold cursor-pointer" style="font-size: 8px;">Clear</button>
|
||||||
|
<button id="jobNoteCollapse" class="px-3 py-1 rounded text-white bg-red-600 hover:bg-red-700 font-bold cursor-pointer" style="font-size: 8px;">Close</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
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 = `
|
||||||
|
<div>
|
||||||
|
<div class="font-bold text-gray-700 uppercase tracking-wide mb-1" style="font-size: 8px;">Plans</div>
|
||||||
|
<div class="text-gray-400 italic" style="font-size: 10px;">Loading...</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="font-bold text-gray-700 uppercase tracking-wide mb-1" style="font-size: 8px;">Estimates</div>
|
||||||
|
<div class="text-gray-400 italic" style="font-size: 10px;">Loading...</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="font-bold text-gray-700 uppercase tracking-wide mb-1" style="font-size: 8px;">Other Files</div>
|
||||||
|
<div class="text-gray-400 italic" style="font-size: 10px;">Loading...</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
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 = '<span class="text-gray-400 italic">No manager notes available</span>';
|
||||||
|
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 = `
|
||||||
|
<div class="flex flex-wrap gap-1 mb-2">
|
||||||
|
<button data-cmd="bold" class="h-6 w-8 rounded border border-gray-400 bg-gray-400 text-gray-900 cursor-pointer flex items-center justify-center" style="font-size: 8px;"><strong>B</strong></button>
|
||||||
|
<button data-cmd="italic" class="h-6 w-8 rounded border border-gray-400 bg-gray-400 text-gray-900 cursor-pointer flex items-center justify-center" style="font-size: 8px;"><em>I</em></button>
|
||||||
|
<button data-cmd="underline" class="h-6 w-8 rounded border border-gray-400 bg-gray-400 text-gray-900 cursor-pointer flex items-center justify-center" style="font-size: 8px;"><u>U</u></button>
|
||||||
|
<button data-cmd="foreColor" data-val="#ff0000" class="h-6 w-8 rounded border border-gray-400 bg-gray-400 text-gray-900 cursor-pointer flex items-center justify-center text-red-500" style="font-size: 8px;">A</button>
|
||||||
|
<button data-cmd="foreColor" data-val="#0000ff" class="h-6 w-8 rounded border border-gray-400 bg-gray-400 text-gray-900 cursor-pointer flex items-center justify-center text-blue-600" style="font-size: 8px;">A</button>
|
||||||
|
<button data-cmd="foreColor" data-val="#000000" class="h-6 w-8 rounded border border-gray-400 bg-gray-400 text-gray-900 cursor-pointer flex items-center justify-center text-black" style="font-size: 8px;">A</button>
|
||||||
|
<button data-cmd="removeFormat" class="h-6 px-2 rounded border border-gray-400 bg-gray-400 text-white cursor-pointer flex items-center justify-center" style="font-size: 8px;">Reset</button>
|
||||||
|
</div>
|
||||||
|
<div id="managerNewNote" contenteditable="true" class="min-h-14 max-h-[120px] overflow-auto p-2 border border-gray-600 rounded text-sm bg-white mb-2"></div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button id="managerNoteSubmit" class="px-3 py-1 rounded text-white bg-green-600 hover:bg-green-700 font-bold cursor-pointer" style="font-size: 8px;">Submit</button>
|
||||||
|
<button id="managerNoteClear" class="px-3 py-1 rounded text-white bg-gray-500 hover:bg-gray-600 font-bold cursor-pointer" style="font-size: 8px;">Clear</button>
|
||||||
|
<button id="managerNoteCollapse" class="px-3 py-1 rounded text-white bg-red-600 hover:bg-red-700 font-bold cursor-pointer" style="font-size: 8px;">Close</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
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 = `
|
||||||
|
<div>
|
||||||
|
<div class="font-bold text-gray-700 uppercase tracking-wide mb-1" style="font-size: 8px;">Plans</div>
|
||||||
|
<div class="text-gray-400 italic" style="font-size: 10px;">Loading...</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="font-bold text-gray-700 uppercase tracking-wide mb-1" style="font-size: 8px;">Estimates</div>
|
||||||
|
<div class="text-gray-400 italic" style="font-size: 10px;">Loading...</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="font-bold text-gray-700 uppercase tracking-wide mb-1" style="font-size: 8px;">Other Files</div>
|
||||||
|
<div class="text-gray-400 italic" style="font-size: 10px;">Loading...</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
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 = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1" style="font-size: 8px;">Plans</div>`;
|
||||||
|
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 = `<div class="text-gray-400 italic" style="font-size: 10px;">No plans</div>`;
|
||||||
|
}
|
||||||
|
plansCol.appendChild(plansList);
|
||||||
|
|
||||||
|
// Estimates column
|
||||||
|
const estimatesCol = document.createElement('div');
|
||||||
|
estimatesCol.innerHTML = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1" style="font-size: 8px;">Estimates</div>`;
|
||||||
|
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 = `<div class="text-gray-400 italic" style="font-size: 10px;">No estimates</div>`;
|
||||||
|
}
|
||||||
|
estimatesCol.appendChild(estimatesList);
|
||||||
|
|
||||||
|
// Other Files column
|
||||||
|
const otherCol = document.createElement('div');
|
||||||
|
otherCol.innerHTML = `<div class="font-bold text-gray-700 uppercase tracking-wide mb-1" style="font-size: 8px;">Other Files</div>`;
|
||||||
|
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 = `<div class="text-gray-400 italic" style="font-size: 10px;">No other files</div>`;
|
||||||
|
}
|
||||||
|
otherCol.appendChild(otherList);
|
||||||
|
|
||||||
|
container.appendChild(plansCol);
|
||||||
|
container.appendChild(estimatesCol);
|
||||||
|
container.appendChild(otherCol);
|
||||||
|
}
|
||||||
|
|
||||||
const closeModal = ()=>{ detailModal.classList.add('hidden'); renderCards(visibleJobs); };
|
const closeModal = ()=>{ detailModal.classList.add('hidden'); renderCards(visibleJobs); };
|
||||||
document.getElementById('btnClose').addEventListener('click',closeModal);
|
document.getElementById('btnClose').addEventListener('click',closeModal);
|
||||||
document.getElementById('backBtn').addEventListener('click',closeModal);
|
document.getElementById('backBtn').addEventListener('click',closeModal);
|
||||||
@@ -1130,30 +1943,53 @@
|
|||||||
function switchTab(tab){
|
function switchTab(tab){
|
||||||
const infoTab = document.getElementById('infoTab');
|
const infoTab = document.getElementById('infoTab');
|
||||||
const notesTab = document.getElementById('notesTab');
|
const notesTab = document.getElementById('notesTab');
|
||||||
|
const managerInfoTab = document.getElementById('managerInfoTab');
|
||||||
const infoContent = document.getElementById('infoContent');
|
const infoContent = document.getElementById('infoContent');
|
||||||
const notesContent = document.getElementById('notesContent');
|
const notesContent = document.getElementById('notesContent');
|
||||||
|
const managerInfoContent = document.getElementById('managerInfoContent');
|
||||||
const infoHeading = document.getElementById('infoHeading');
|
const infoHeading = document.getElementById('infoHeading');
|
||||||
const notesHeading = document.getElementById('notesHeading');
|
const notesHeading = document.getElementById('notesHeading');
|
||||||
|
const managerInfoHeading = document.getElementById('managerInfoHeading');
|
||||||
|
|
||||||
if(tab === 'info'){
|
if(tab === 'info'){
|
||||||
infoTab.className = 'px-4 py-2 text-sm font-medium text-blue-600 border-b-2 border-blue-600 -mb-px';
|
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';
|
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');
|
infoContent.classList.remove('hidden');
|
||||||
notesContent.classList.add('hidden');
|
notesContent.classList.add('hidden');
|
||||||
|
managerInfoContent.classList.add('hidden');
|
||||||
if(infoHeading) infoHeading.classList.remove('hidden');
|
if(infoHeading) infoHeading.classList.remove('hidden');
|
||||||
if(notesHeading) notesHeading.classList.add('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';
|
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';
|
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');
|
infoContent.classList.add('hidden');
|
||||||
notesContent.classList.remove('hidden');
|
notesContent.classList.remove('hidden');
|
||||||
|
managerInfoContent.classList.add('hidden');
|
||||||
if(infoHeading) infoHeading.classList.add('hidden');
|
if(infoHeading) infoHeading.classList.add('hidden');
|
||||||
if(notesHeading) notesHeading.classList.remove('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('infoTab').addEventListener('click',()=>switchTab('info'));
|
||||||
document.getElementById('notesTab').addEventListener('click',()=>switchTab('notes'));
|
document.getElementById('notesTab').addEventListener('click',()=>switchTab('notes'));
|
||||||
|
document.getElementById('managerInfoTab').addEventListener('click',()=>{
|
||||||
|
switchTab('managerInfo');
|
||||||
|
populateManagerInfo(currentJob);
|
||||||
|
});
|
||||||
|
|
||||||
// --- Toolbar ---
|
// --- Toolbar ---
|
||||||
let savedSelection = null;
|
let savedSelection = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user