made updates to manager info view
This commit is contained in:
+239
-31
@@ -202,7 +202,7 @@
|
|||||||
<div class="flex-1 relative bg-white flex flex-col overflow-hidden">
|
<div class="flex-1 relative bg-white flex flex-col overflow-hidden">
|
||||||
<div id="fileListContainer" class="hidden flex-1 flex flex-col p-0 bg-white overflow-hidden"></div>
|
<div id="fileListContainer" class="hidden flex-1 flex flex-col p-0 bg-white overflow-hidden"></div>
|
||||||
<div id="pdfViewer" class="hidden flex-1 flex flex-col bg-gray-50">
|
<div id="pdfViewer" class="hidden flex-1 flex flex-col bg-gray-50">
|
||||||
<div class="flex items-center gap-2 px-3 py-2 border-b border-gray-200 bg-white shadow-sm flex-shrink-0">
|
<div id="pdfControls" class="flex items-center gap-2 px-3 py-2 border-b border-gray-200 bg-white shadow-sm flex-shrink-0">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<button id="pdfZoomOut" class="px-2 py-1 bg-gray-200 hover:bg-gray-300 rounded">−</button>
|
<button id="pdfZoomOut" class="px-2 py-1 bg-gray-200 hover:bg-gray-300 rounded">−</button>
|
||||||
<button id="pdfZoomReset" class="px-2 py-1 bg-gray-200 hover:bg-gray-300 rounded">45%</button>
|
<button id="pdfZoomReset" class="px-2 py-1 bg-gray-200 hover:bg-gray-300 rounded">45%</button>
|
||||||
@@ -1032,7 +1032,7 @@
|
|||||||
|
|
||||||
const col1 = document.createElement('div'); col1.className='flex-1';
|
const col1 = document.createElement('div'); col1.className='flex-1';
|
||||||
const label1 = document.createElement('div'); label1.className='text-xs font-bold text-gray-700 uppercase tracking-wide mb-1'; label1.textContent='Manager';
|
const label1 = document.createElement('div'); label1.className='text-xs font-bold text-gray-700 uppercase tracking-wide mb-1'; label1.textContent='Manager';
|
||||||
const val1 = document.createElement('div'); val1.className='text-sm text-gray-900'; val1.textContent = job['Manager'] || '—';
|
const val1 = document.createElement('div'); val1.className='text-sm text-gray-900'; val1.textContent = job['Project_Manager'] || '—';
|
||||||
col1.appendChild(label1); col1.appendChild(val1);
|
col1.appendChild(label1); col1.appendChild(val1);
|
||||||
|
|
||||||
const col2 = document.createElement('div'); col2.className='flex-1';
|
const col2 = document.createElement('div'); col2.className='flex-1';
|
||||||
@@ -1145,7 +1145,7 @@
|
|||||||
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>`;
|
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');
|
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>`;
|
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.Project_Manager || '—'}</div>`;
|
||||||
|
|
||||||
const jobStatusDiv = document.createElement('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>`;
|
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>`;
|
||||||
@@ -1169,7 +1169,7 @@
|
|||||||
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>`;
|
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');
|
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>`;
|
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;">${job.Scope || '—'}</div>`;
|
||||||
|
|
||||||
row2.appendChild(jobNum);
|
row2.appendChild(jobNum);
|
||||||
row2.appendChild(jobDivision);
|
row2.appendChild(jobDivision);
|
||||||
@@ -1206,7 +1206,7 @@
|
|||||||
contactNumber.appendChild(phoneDiv);
|
contactNumber.appendChild(phoneDiv);
|
||||||
|
|
||||||
const extn = document.createElement('div');
|
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>`;
|
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;">${job.EXT || '—'}</div>`;
|
||||||
|
|
||||||
row3.appendChild(companyClient);
|
row3.appendChild(companyClient);
|
||||||
row3.appendChild(contactPerson);
|
row3.appendChild(contactPerson);
|
||||||
@@ -1253,7 +1253,14 @@
|
|||||||
|
|
||||||
// EST Cost in 4th column (centered)
|
// EST Cost in 4th column (centered)
|
||||||
const estimatedCostContainer = document.createElement('div');
|
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>`;
|
let costFormatted = '—';
|
||||||
|
if(job.Estimated_Cost !== null && job.Estimated_Cost !== undefined && job.Estimated_Cost !== '') {
|
||||||
|
const costNum = parseFloat(job.Estimated_Cost);
|
||||||
|
if(!isNaN(costNum)) {
|
||||||
|
costFormatted = '$' + costNum.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;">${costFormatted}</div>`;
|
||||||
|
|
||||||
row4.appendChild(addressContainer);
|
row4.appendChild(addressContainer);
|
||||||
row4.appendChild(mapIconsContainer);
|
row4.appendChild(mapIconsContainer);
|
||||||
@@ -1278,11 +1285,29 @@
|
|||||||
const estimatedStartDate = document.createElement('div');
|
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>`;
|
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>`;
|
||||||
|
|
||||||
|
let actualStartDateFormatted = '—';
|
||||||
|
if(job.Start_Date_Actual){
|
||||||
|
try {
|
||||||
|
const date = new Date(job.Start_Date_Actual);
|
||||||
|
actualStartDateFormatted = date.toLocaleDateString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit' });
|
||||||
|
} catch(e) {
|
||||||
|
actualStartDateFormatted = job.Start_Date_Actual;
|
||||||
|
}
|
||||||
|
}
|
||||||
const actualStartDate = document.createElement('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>`;
|
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;">${actualStartDateFormatted}</div>`;
|
||||||
|
|
||||||
|
let estimatedEndDateFormatted = '—';
|
||||||
|
if(job.Estimated_End_Date){
|
||||||
|
try {
|
||||||
|
const date = new Date(job.Estimated_End_Date);
|
||||||
|
estimatedEndDateFormatted = date.toLocaleDateString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit' });
|
||||||
|
} catch(e) {
|
||||||
|
estimatedEndDateFormatted = job.Estimated_End_Date;
|
||||||
|
}
|
||||||
|
}
|
||||||
const estimatedEndDate = document.createElement('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>`;
|
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;">${estimatedEndDateFormatted}</div>`;
|
||||||
|
|
||||||
row5.appendChild(estimatedStartDate);
|
row5.appendChild(estimatedStartDate);
|
||||||
row5.appendChild(actualStartDate);
|
row5.appendChild(actualStartDate);
|
||||||
@@ -1441,6 +1466,12 @@
|
|||||||
<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="#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="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>
|
<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>
|
||||||
|
<button id="jobNoteMic" class="h-6 w-8 rounded border border-gray-400 bg-gray-400 cursor-pointer flex items-center justify-center" title="Voice to text">
|
||||||
|
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="#4B5563">
|
||||||
|
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z"/>
|
||||||
|
<path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</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 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">
|
<div class="flex gap-2">
|
||||||
@@ -1505,6 +1536,12 @@
|
|||||||
<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="#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="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>
|
<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>
|
||||||
|
<button id="managerNoteMic" class="h-6 w-8 rounded border border-gray-400 bg-gray-400 cursor-pointer flex items-center justify-center" title="Voice to text">
|
||||||
|
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="#4B5563">
|
||||||
|
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z"/>
|
||||||
|
<path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</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 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">
|
<div class="flex gap-2">
|
||||||
@@ -1597,12 +1634,21 @@
|
|||||||
populateNotesFileColumns([], 'managerNotesFileColumns');
|
populateNotesFileColumns([], 'managerNotesFileColumns');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load and display notes filtered by type
|
||||||
|
loadAndDisplayNotes(job);
|
||||||
|
|
||||||
// Setup toolbar functionality for job notes editor
|
// Setup toolbar functionality for job notes editor
|
||||||
setupNoteEditorToolbar('jobNewNote', 'jobNoteEditor');
|
setupNoteEditorToolbar('jobNewNote', 'jobNoteEditor');
|
||||||
|
|
||||||
// Setup toolbar functionality for manager notes editor
|
// Setup toolbar functionality for manager notes editor
|
||||||
setupNoteEditorToolbar('managerNewNote', 'managerNoteEditor');
|
setupNoteEditorToolbar('managerNewNote', 'managerNoteEditor');
|
||||||
|
|
||||||
|
// Setup voice-to-text for job notes
|
||||||
|
setupVoiceToText('jobNoteMic', 'jobNewNote');
|
||||||
|
|
||||||
|
// Setup voice-to-text for manager notes
|
||||||
|
setupVoiceToText('managerNoteMic', 'managerNewNote');
|
||||||
|
|
||||||
// Setup close functionality
|
// Setup close functionality
|
||||||
document.getElementById('jobNoteCollapse').addEventListener('click', (e) => {
|
document.getElementById('jobNoteCollapse').addEventListener('click', (e) => {
|
||||||
console.log('Close job note editor');
|
console.log('Close job note editor');
|
||||||
@@ -1668,6 +1714,102 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup voice-to-text functionality
|
||||||
|
function setupVoiceToText(micButtonId, editorId) {
|
||||||
|
const micBtn = document.getElementById(micButtonId);
|
||||||
|
const editor = document.getElementById(editorId);
|
||||||
|
|
||||||
|
if(!micBtn || !editor) {
|
||||||
|
console.log('Mic button or editor not found:', micButtonId, editorId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for browser support
|
||||||
|
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||||
|
if(!SpeechRecognition) {
|
||||||
|
micBtn.disabled = true;
|
||||||
|
micBtn.title = 'Speech recognition not supported';
|
||||||
|
micBtn.style.opacity = '0.5';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recognition = new SpeechRecognition();
|
||||||
|
recognition.continuous = true;
|
||||||
|
recognition.interimResults = true;
|
||||||
|
recognition.lang = 'en-US';
|
||||||
|
|
||||||
|
let isListening = false;
|
||||||
|
const micIcon = micBtn.querySelector('svg');
|
||||||
|
|
||||||
|
micBtn.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if(isListening) {
|
||||||
|
recognition.stop();
|
||||||
|
isListening = false;
|
||||||
|
micIcon.setAttribute('fill', '#4B5563'); // Gray (inactive)
|
||||||
|
micBtn.style.backgroundColor = '';
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
recognition.start();
|
||||||
|
isListening = true;
|
||||||
|
micIcon.setAttribute('fill', '#EF4444'); // Red (recording)
|
||||||
|
micBtn.style.backgroundColor = '#FEE2E2'; // Light red background
|
||||||
|
} catch(err) {
|
||||||
|
console.error('Recognition start error:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
recognition.onresult = (event) => {
|
||||||
|
let interimTranscript = '';
|
||||||
|
let finalTranscript = '';
|
||||||
|
|
||||||
|
for(let i = event.resultIndex; i < event.results.length; i++) {
|
||||||
|
const transcript = event.results[i][0].transcript;
|
||||||
|
if(event.results[i].isFinal) {
|
||||||
|
finalTranscript += transcript + ' ';
|
||||||
|
} else {
|
||||||
|
interimTranscript += transcript;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(finalTranscript) {
|
||||||
|
// Insert final transcript at cursor position
|
||||||
|
editor.focus();
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if(selection.rangeCount > 0) {
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
range.deleteContents();
|
||||||
|
range.insertNode(document.createTextNode(finalTranscript));
|
||||||
|
range.collapse(false);
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
} else {
|
||||||
|
// Fallback: append to end
|
||||||
|
editor.innerHTML += finalTranscript;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
recognition.onerror = (event) => {
|
||||||
|
console.error('Speech recognition error:', event.error);
|
||||||
|
isListening = false;
|
||||||
|
micIcon.setAttribute('fill', '#4B5563');
|
||||||
|
micBtn.style.backgroundColor = '';
|
||||||
|
|
||||||
|
if(event.error === 'not-allowed') {
|
||||||
|
alert('Microphone access denied. Please allow microphone access in your browser settings.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
recognition.onend = () => {
|
||||||
|
isListening = false;
|
||||||
|
micIcon.setAttribute('fill', '#4B5563');
|
||||||
|
micBtn.style.backgroundColor = '';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Setup toolbar for note editors
|
// Setup toolbar for note editors
|
||||||
function setupNoteEditorToolbar(editorId, containerId) {
|
function setupNoteEditorToolbar(editorId, containerId) {
|
||||||
const editor = document.getElementById(editorId);
|
const editor = document.getElementById(editorId);
|
||||||
@@ -1756,30 +1898,40 @@
|
|||||||
hour12: true
|
hour12: true
|
||||||
});
|
});
|
||||||
const currentUserName = getDisplayName();
|
const currentUserName = getDisplayName();
|
||||||
const noteType = isManagerNote ? '[MANAGER NOTE]' : '';
|
const noteType = isManagerNote ? 'manager' : 'job';
|
||||||
const formatted = `${noteType}[${currentUserName} - ${timestamp}] [Job #${job?.Job_Number||'N/A'}] ${html}\n\n`;
|
const typeLabel = isManagerNote ? '[MANAGER NOTE]' : '';
|
||||||
const updatedNotes = formatted + (job.Notes || '');
|
const formatted = `${typeLabel}[${currentUserName} - ${timestamp}] [Job #${job?.Job_Number||'N/A'}] ${html}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payloadNote = {
|
const payloadNote = {
|
||||||
Note: formatted.trim(),
|
Note: formatted.trim(),
|
||||||
Job_Number: job?.Job_Number || '',
|
Job_Number: job?.Job_Number || '',
|
||||||
Job_Number_Id: job?.id ? [job.id] : []
|
Job_Number_Id: job?.id ? [job.id] : [],
|
||||||
|
type: noteType
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update job record
|
// If it's a job note, also update Job_Info_Prod.Notes field
|
||||||
const resJob = await fetch(`${PB_COLLECTION_URL}/${job.id}`, {
|
if(!isManagerNote) {
|
||||||
method: 'PATCH',
|
const updatedNotes = formatted + '\n\n' + (job.Notes || '');
|
||||||
headers: {'Content-Type': 'application/json', ...authHeaders()},
|
const resJob = await fetch(`${PB_COLLECTION_URL}/${job.id}`, {
|
||||||
body: JSON.stringify({Notes: updatedNotes})
|
method: 'PATCH',
|
||||||
});
|
headers: {'Content-Type': 'application/json', ...authHeaders()},
|
||||||
|
body: JSON.stringify({Notes: updatedNotes})
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!resJob.ok) {
|
||||||
|
console.error('Job_Info update failed:', resJob.status);
|
||||||
|
throw new Error('Failed to update Job_Info: ' + resJob.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = await resJob.json();
|
||||||
|
payloadNote.Job_Number_Id = [updated.id || job.id];
|
||||||
|
|
||||||
|
// Update current job object
|
||||||
|
currentJob.Notes = updatedNotes;
|
||||||
|
}
|
||||||
|
|
||||||
if(!resJob.ok) throw new Error('Job_Info update failed: ' + resJob.status);
|
// Create note in notes collection with type
|
||||||
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}`, {
|
const resNote = await fetch(`${NOTES_COLLECTION_URL}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json', ...authHeaders()},
|
headers: {'Content-Type': 'application/json', ...authHeaders()},
|
||||||
@@ -1787,17 +1939,23 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if(!resNote.ok) {
|
if(!resNote.ok) {
|
||||||
console.warn('Note creation failed with status', resNote.status);
|
const errText = await resNote.text().catch(() => '');
|
||||||
|
console.error('Note creation failed:', resNote.status, errText);
|
||||||
|
// Don't throw if Notes collection fails but Job_Info succeeded
|
||||||
|
if(!isManagerNote) {
|
||||||
|
console.warn('Note saved to Job_Info but failed to save to Notes collection');
|
||||||
|
} else {
|
||||||
|
throw new Error('Failed to save note: ' + resNote.status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update UI
|
// Clear editor
|
||||||
currentJob.Notes = updatedNotes;
|
|
||||||
editor.innerHTML = '';
|
editor.innerHTML = '';
|
||||||
alert('Note saved successfully!');
|
alert('Note saved successfully!');
|
||||||
|
|
||||||
// Refresh the display
|
// Refresh the display
|
||||||
if(currentJob) {
|
if(currentJob) {
|
||||||
populateManagerInfo(currentJob);
|
await loadAndDisplayNotes(currentJob);
|
||||||
}
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.error('Error saving note:', err);
|
console.error('Error saving note:', err);
|
||||||
@@ -1805,6 +1963,55 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load and display notes filtered by type
|
||||||
|
async function loadAndDisplayNotes(job) {
|
||||||
|
if(!job?.id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch all notes for this job
|
||||||
|
const filter = `Job_Number_Id?~"${job.id}"`;
|
||||||
|
const resp = await fetch(`${NOTES_COLLECTION_URL}?filter=${encodeURIComponent(filter)}&sort=-created`, {
|
||||||
|
headers: authHeaders()
|
||||||
|
});
|
||||||
|
|
||||||
|
if(!resp.ok) {
|
||||||
|
console.error('Failed to fetch notes:', resp.status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await resp.json();
|
||||||
|
const allNotes = data.items || [];
|
||||||
|
|
||||||
|
// Filter notes by type
|
||||||
|
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');
|
||||||
|
if(jobNotesDisplay) {
|
||||||
|
if(jobNotes.length > 0) {
|
||||||
|
jobNotesDisplay.innerHTML = jobNotes.map(n => n.Note).join('\n\n');
|
||||||
|
} else {
|
||||||
|
jobNotesDisplay.innerHTML = '<span class="text-gray-400 italic">No notes available</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update manager notes display
|
||||||
|
const managerNotesDisplay = document.querySelector('#managerNotesContent > div:first-child');
|
||||||
|
if(managerNotesDisplay) {
|
||||||
|
if(managerNotes.length > 0) {
|
||||||
|
managerNotesDisplay.innerHTML = managerNotes.map(n => n.Note).join('\n\n');
|
||||||
|
} else {
|
||||||
|
managerNotesDisplay.innerHTML = '<span class="text-gray-400 italic">No manager notes available</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Loaded notes - Job:', jobNotes.length, 'Manager:', managerNotes.length);
|
||||||
|
} catch(err) {
|
||||||
|
console.error('Error loading notes:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Function to populate file columns in notes section
|
// Function to populate file columns in notes section
|
||||||
function populateNotesFileColumns(files, containerId) {
|
function populateNotesFileColumns(files, containerId) {
|
||||||
const container = document.getElementById(containerId);
|
const container = document.getElementById(containerId);
|
||||||
@@ -1855,7 +2062,7 @@
|
|||||||
fileName.className = 'truncate';
|
fileName.className = 'truncate';
|
||||||
fileItem.appendChild(fileName);
|
fileItem.appendChild(fileName);
|
||||||
|
|
||||||
fileItem.onclick = () => openFilePreview(file);
|
fileItem.onclick = () => openFilePreview(file.driveId || '', file.id || '', file.name, file.contentType || '', file.url);
|
||||||
plansList.appendChild(fileItem);
|
plansList.appendChild(fileItem);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -1884,7 +2091,7 @@
|
|||||||
fileName.className = 'truncate';
|
fileName.className = 'truncate';
|
||||||
fileItem.appendChild(fileName);
|
fileItem.appendChild(fileName);
|
||||||
|
|
||||||
fileItem.onclick = () => openFilePreview(file);
|
fileItem.onclick = () => openFilePreview(file.driveId || '', file.id || '', file.name, file.contentType || '', file.url);
|
||||||
estimatesList.appendChild(fileItem);
|
estimatesList.appendChild(fileItem);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -1922,7 +2129,7 @@
|
|||||||
fileName.className = 'truncate';
|
fileName.className = 'truncate';
|
||||||
fileItem.appendChild(fileName);
|
fileItem.appendChild(fileName);
|
||||||
|
|
||||||
fileItem.onclick = () => openFilePreview(file);
|
fileItem.onclick = () => openFilePreview(file.driveId || '', file.id || '', file.name, file.contentType || '', file.url);
|
||||||
otherList.appendChild(fileItem);
|
otherList.appendChild(fileItem);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -2105,6 +2312,7 @@
|
|||||||
let pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
|
let pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
|
||||||
const pdfViewer = document.getElementById('pdfViewer');
|
const pdfViewer = document.getElementById('pdfViewer');
|
||||||
const pdfCanvas = document.getElementById('pdfCanvas');
|
const pdfCanvas = document.getElementById('pdfCanvas');
|
||||||
|
const pdfControls = document.getElementById('pdfControls');
|
||||||
const pdfPageNum = document.getElementById('pdfPageNum');
|
const pdfPageNum = document.getElementById('pdfPageNum');
|
||||||
const pdfPageCount = document.getElementById('pdfPageCount');
|
const pdfPageCount = document.getElementById('pdfPageCount');
|
||||||
const pdfPrev = document.getElementById('pdfPrev');
|
const pdfPrev = document.getElementById('pdfPrev');
|
||||||
|
|||||||
Reference in New Issue
Block a user