+
+
+
+
INFO
+
+
+
+
-
+
+
+
NOTES
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -215,11 +152,11 @@
if(simulatedProg<65){ simulatedProg+=0.8; progressBar.style.width=Math.min(65,simulatedProg).toFixed(1)+'%'; }
},150);
}
- function finishProgress(){ if(progTimer) clearInterval(progTimer); progressBar.style.width='100%'; setTimeout(()=>{progressBar.parentElement.style.display='none';},400); }
+ function finishProgress(){ if(progTimer) clearInterval(progTimer); progressBar.style.width='100%'; setTimeout(()=>{progressBar.parentElement.classList.add('hidden');},400); }
// --- Fetch all jobs ---
async function fetchAllJobs(){
- startProgress(); jobsCache=[]; visibleJobs=[]; resultsEl.innerHTML = '
Loading…
';
+ startProgress(); jobsCache=[]; visibleJobs=[]; resultsEl.innerHTML = '
Loading…
';
let page=1, totalItems=0; let firstBatchRendered=false;
try{
while(true){
@@ -251,13 +188,13 @@
function renderInitialBatch(jobs){
resultsEl.innerHTML='';
for(const job of jobs){
- const card=document.createElement('div'); card.className='card';
+ const card=document.createElement('div'); card.className='bg-white rounded-lg p-2.5 border border-blue-100 shadow-sm cursor-pointer relative flex flex-col justify-center gap-1';
card.innerHTML=`
-
Job #:
${escapeHtml(job.Job_Number||'')}
-
Job Name:
${escapeHtml(job.Job_Full_Name||'')}
-
Contact:
${escapeHtml(job.Contact_Person||'')}
+
Job #:
${escapeHtml(job.Job_Number||'')}
+
Job Name:
${escapeHtml(job.Job_Full_Name||'')}
+
Contact:
${escapeHtml(job.Contact_Person||'')}
`;
- const dot=document.createElement('div'); dot.className='active-dot';
+ const dot=document.createElement('div'); dot.className='absolute top-2.5 right-2.5 w-3.5 h-3.5 rounded-full border-2 border-white shadow-sm';
dot.style.background=(String(job.Active||'').toLowerCase()==='yes')?'green':((String(job.Active||'').toLowerCase()==='no')?'red':'#cbd5e1');
card.appendChild(dot);
card.addEventListener('click',()=>openDetail(job));
@@ -310,19 +247,19 @@
function renderCards(list){
resultsEl.innerHTML='';
- if(!list.length){ resultsEl.innerHTML = `
No results
`; return; }
+ if(!list.length){ resultsEl.innerHTML = `
No results
`; return; }
const firstChunk = list.slice(0,20);
const remaining = list.slice(20);
for(const job of firstChunk){
- const card=document.createElement('div'); card.className='card';
+ const card=document.createElement('div'); card.className='bg-white rounded-lg p-2.5 border border-blue-100 shadow-sm cursor-pointer relative flex flex-col justify-center gap-1';
card.innerHTML=`
-
Job #:
${escapeHtml(job.Job_Number||'')}
-
Job Name:
${escapeHtml(job.Job_Full_Name||'')}
-
Contact:
${escapeHtml(job.Contact_Person||'')}
+
Job #:
${escapeHtml(job.Job_Number||'')}
+
Job Name:
${escapeHtml(job.Job_Full_Name||'')}
+
Contact:
${escapeHtml(job.Contact_Person||'')}
`;
- const dot=document.createElement('div'); dot.className='active-dot';
+ const dot=document.createElement('div'); dot.className='absolute top-2.5 right-2.5 w-3.5 h-3.5 rounded-full border-2 border-white shadow-sm';
dot.style.background=(String(job.Active||'').toLowerCase()==='yes')?'green':((String(job.Active||'').toLowerCase()==='no')?'red':'#cbd5e1');
card.appendChild(dot);
card.addEventListener('click',()=>openDetail(job));
@@ -332,13 +269,13 @@
if(remaining.length){
setTimeout(()=>{
for(const job of remaining){
- const card=document.createElement('div'); card.className='card';
+ const card=document.createElement('div'); card.className='bg-white rounded-lg p-2.5 border border-blue-100 shadow-sm cursor-pointer relative flex flex-col justify-center gap-1';
card.innerHTML=`
-
Job #:
${escapeHtml(job.Job_Number||'')}
-
Job Name:
${escapeHtml(job.Job_Full_Name||'')}
-
Contact:
${escapeHtml(job.Contact_Person||'')}
+
Job #:
${escapeHtml(job.Job_Number||'')}
+
Job Name:
${escapeHtml(job.Job_Full_Name||'')}
+
Contact:
${escapeHtml(job.Contact_Person||'')}
`;
- const dot=document.createElement('div'); dot.className='active-dot';
+ const dot=document.createElement('div'); dot.className='absolute top-2.5 right-2.5 w-3.5 h-3.5 rounded-full border-2 border-white shadow-sm';
dot.style.background=(String(job.Active||'').toLowerCase()==='yes')?'green':((String(job.Active||'').toLowerCase()==='no')?'red':'#cbd5e1');
card.appendChild(dot);
card.addEventListener('click',()=>openDetail(job));
@@ -350,7 +287,7 @@
function escapeHtml(str){ return String(str).replaceAll('&','&').replaceAll('<','<').replaceAll('>','>'); }
// --- Filters ---
- filterToggle.addEventListener('click',()=>{ filtersPanel.style.display = filtersPanel.style.display==='block'?'none':'block'; filterToggle.textContent = filtersPanel.style.display==='block'?'Hide Filters':'Show Filters'; });
+ filterToggle.addEventListener('click',()=>{ filtersPanel.classList.toggle('hidden'); filterToggle.textContent = filtersPanel.classList.contains('hidden')?'Show Filters':'Hide Filters'; });
searchBox.addEventListener('input',()=>applyFiltersAndRender());
document.querySelectorAll('.filter-division').forEach(cb=>cb.addEventListener('change',()=>applyFiltersAndRender()));
document.querySelectorAll('.filter-active').forEach(cb=>{ cb.addEventListener('change',()=>{
@@ -370,11 +307,121 @@
currentJob = job;
detailTable.innerHTML = '';
const fields = ["Job_Full_Name","Job_Number","Company_Client","Manager","Contact_Person","Phone_Number","Job_Address","Start_Date","Tax_Exempt","Job_Status"];
- for(const f of fields){
- const tr = document.createElement('tr');
- const tdLabel = document.createElement('td'); tdLabel.className='label'; tdLabel.textContent=f.replace(/_/g,' ');
- const tdVal = document.createElement('td'); tdVal.textContent=job[f]||'';
- tr.appendChild(tdLabel); tr.appendChild(tdVal); detailTable.appendChild(tr);
+ let i = 0;
+ while(i < fields.length){
+ const f = fields[i];
+
+ // Handle paired fields
+ if(f === 'Job_Number'){
+ const row = document.createElement('div'); row.className='pb-2 border-b border-gray-100 flex gap-4';
+
+ // Job Number
+ const col1 = document.createElement('div'); col1.className='flex-1';
+ const label1 = document.createElement('div'); label1.className='text-xs font-bold text-gray-500 uppercase tracking-wide mb-1'; label1.textContent='Job Number';
+ const val1 = document.createElement('div'); val1.className='text-sm text-gray-900'; val1.textContent = job['Job_Number'] || '—';
+ col1.appendChild(label1); col1.appendChild(val1);
+
+ // Company Client
+ const col2 = document.createElement('div'); col2.className='flex-1';
+ const label2 = document.createElement('div'); label2.className='text-xs font-bold text-gray-500 uppercase tracking-wide mb-1'; label2.textContent='Company Client';
+ const val2 = document.createElement('div'); val2.className='text-sm text-gray-900'; val2.textContent = job['Company_Client'] || '—';
+ col2.appendChild(label2); col2.appendChild(val2);
+
+ row.appendChild(col1); row.appendChild(col2); detailTable.appendChild(row);
+ i += 2; // Skip Company_Client in next iteration
+ continue;
+ }
+
+ if(f === 'Contact_Person'){
+ const row = document.createElement('div'); row.className='pb-2 border-b border-gray-100 flex gap-4';
+
+ // Contact Person
+ const col1 = document.createElement('div'); col1.className='flex-1';
+ const label1 = document.createElement('div'); label1.className='text-xs font-bold text-gray-500 uppercase tracking-wide mb-1'; label1.textContent='Contact Person';
+ const val1 = document.createElement('div'); val1.className='text-sm text-gray-900'; val1.textContent = job['Contact_Person'] || '—';
+ col1.appendChild(label1); col1.appendChild(val1);
+
+ // Phone Number
+ const col2 = document.createElement('div'); col2.className='flex-1';
+ const label2 = document.createElement('div'); label2.className='text-xs font-bold text-gray-500 uppercase tracking-wide mb-1'; label2.textContent='Phone Number';
+ const val2 = document.createElement('div'); val2.className='text-sm';
+ if(job['Phone_Number'] && job['Phone_Number'].trim() !== ''){
+ const phoneLink = document.createElement('a');
+ const phoneNumber = job['Phone_Number'].replace(/\D/g, ''); // Remove non-digits
+ phoneLink.href = `tel:${phoneNumber}`;
+ phoneLink.className = 'text-blue-600 hover:text-blue-800 underline';
+ phoneLink.textContent = job['Phone_Number'];
+ val2.appendChild(phoneLink);
+ } else {
+ val2.className = 'text-sm text-gray-900';
+ val2.textContent = '—';
+ }
+ col2.appendChild(label2); col2.appendChild(val2);
+
+ row.appendChild(col1); row.appendChild(col2); detailTable.appendChild(row);
+ i += 2; // Skip Phone_Number in next iteration
+ continue;
+ }
+
+ // Regular single field
+ const value = job[f] || '';
+ if(!value && f === 'Tax_Exempt') {
+ i++;
+ continue; // Skip empty Tax Exempt
+ }
+
+ const row = document.createElement('div'); row.className='pb-2 border-b border-gray-100 last:border-0';
+ const label = document.createElement('div'); label.className='text-xs font-bold text-gray-500 uppercase tracking-wide mb-1'; label.textContent=f.replace(/_/g,' ');
+ const val = document.createElement('div'); val.className='text-sm text-gray-900';
+
+ // Format date if it's Start_Date
+ if(f === 'Start_Date' && value){
+ try {
+ const date = new Date(value);
+ val.textContent = date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
+ } catch(e) {
+ val.textContent = value;
+ }
+ } else if(f === 'Job_Address' && value && value.trim() !== ''){
+ // Create address container with text and map icons
+ val.className = 'text-sm flex items-center gap-2 flex-wrap';
+
+ // Address text
+ const addressText = document.createElement('span');
+ addressText.className = 'text-gray-900 flex-1 min-w-0';
+ addressText.textContent = value;
+ val.appendChild(addressText);
+
+ // Icon container
+ const iconContainer = document.createElement('div');
+ iconContainer.className = 'flex items-center gap-2';
+
+ // Apple Maps link
+ const appleLink = document.createElement('a');
+ const encodedAddress = encodeURIComponent(value);
+ appleLink.href = `https://maps.apple.com/?q=${encodedAddress}`;
+ appleLink.target = '_blank';
+ appleLink.className = 'inline-flex items-center justify-center w-6 h-6 hover:opacity-80 transition-opacity';
+ appleLink.title = 'Open in Apple Maps';
+ appleLink.innerHTML = '
';
+ iconContainer.appendChild(appleLink);
+
+ // Google Maps link
+ 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-6 h-6 hover:opacity-80 transition-opacity';
+ googleLink.title = 'Open in Google Maps';
+ googleLink.innerHTML = '
';
+ iconContainer.appendChild(googleLink);
+
+ val.appendChild(iconContainer);
+ } else {
+ val.textContent = value || '—';
+ }
+
+ row.appendChild(label); row.appendChild(val); detailTable.appendChild(row);
+ i++;
}
// Job folder button
@@ -383,28 +430,61 @@
const url=new URL(job.Job_Folder_Link);
jobFolderBtn.textContent='Show Job Folder';
jobFolderBtn.disabled=false;
- jobFolderBtn.className='active';
+ jobFolderBtn.className='w-full py-2.5 px-3 rounded-lg border-none text-white cursor-pointer text-[17px] font-bold mb-2.5 transition-colors duration-200 bg-blue-600 hover:bg-blue-700';
jobFolderBtn.onclick=()=>window.open(url.href, '_blank', 'noopener,noreferrer');
}catch(e){
jobFolderBtn.textContent='No Folder Available';
jobFolderBtn.disabled=true;
- jobFolderBtn.className='';
+ jobFolderBtn.className='w-full py-2.5 px-3 rounded-lg border-none text-white cursor-pointer text-[17px] font-bold mb-2.5 transition-colors duration-200 bg-gray-600 cursor-not-allowed opacity-70';
jobFolderBtn.onclick=null;
}
} else {
jobFolderBtn.textContent='No Folder Available';
jobFolderBtn.disabled=true;
- jobFolderBtn.className='';
+ jobFolderBtn.className='w-full py-2.5 px-3 rounded-lg border-none text-white cursor-pointer text-[17px] font-bold mb-2.5 transition-colors duration-200 bg-gray-600 cursor-not-allowed opacity-70';
jobFolderBtn.onclick=null;
}
jobNotes.innerHTML = job.Notes || '';
newNote.innerHTML = '';
- tbButtons.forEach(b=>b.classList.remove('active'));
- detailModal.style.display='flex';
+ tbButtons.forEach(b=>b.classList.remove('bg-blue-100', 'text-gray-900', 'border-blue-600'));
+ detailModal.classList.remove('hidden');
document.getElementById('detailBox').scrollTop = 0;
+
+ // Reset to INFO tab when opening
+ switchTab('info');
}
- document.getElementById('btnClose').addEventListener('click',()=>{detailModal.style.display='none';});
+ document.getElementById('btnClose').addEventListener('click',()=>{detailModal.classList.add('hidden');});
+ document.getElementById('backBtn').addEventListener('click',()=>{detailModal.classList.add('hidden');});
+
+ // Tab switching
+ function switchTab(tab){
+ const infoTab = document.getElementById('infoTab');
+ const notesTab = document.getElementById('notesTab');
+ const infoContent = document.getElementById('infoContent');
+ const notesContent = document.getElementById('notesContent');
+ const infoHeading = document.getElementById('infoHeading');
+ const notesHeading = document.getElementById('notesHeading');
+
+ 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';
+ infoContent.classList.remove('hidden');
+ notesContent.classList.add('hidden');
+ if(infoHeading) infoHeading.classList.remove('hidden');
+ if(notesHeading) notesHeading.classList.add('hidden');
+ } else {
+ 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';
+ infoContent.classList.add('hidden');
+ notesContent.classList.remove('hidden');
+ if(infoHeading) infoHeading.classList.add('hidden');
+ if(notesHeading) notesHeading.classList.remove('hidden');
+ }
+ }
+
+ document.getElementById('infoTab').addEventListener('click',()=>switchTab('info'));
+ document.getElementById('notesTab').addEventListener('click',()=>switchTab('notes'));
// --- Toolbar ---
let savedSelection = null;
@@ -417,9 +497,9 @@
restoreSelection();
const cmd = btn.dataset.cmd;
if(!cmd) return;
- if(cmd==='foreColor'){ const val=btn.dataset.val; document.execCommand(cmd,false,val); tbButtons.forEach(b=>{if(b.dataset.cmd==='foreColor'&&b!==btn)b.classList.remove('active');}); btn.classList.toggle('active'); }
- 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); } tbButtons.forEach(b=>b.classList.remove('active')); }
- else{ document.execCommand(cmd,false,null); btn.classList.toggle('active'); }
+ if(cmd==='foreColor'){ const val=btn.dataset.val; document.execCommand(cmd,false,val); tbButtons.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); } tbButtons.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'); }
newNote.focus(); saveSelection();
});
});
diff --git a/frontend/signin.html b/frontend/signin.html
index 6491a90..8a3abe1 100644
--- a/frontend/signin.html
+++ b/frontend/signin.html
@@ -4,143 +4,26 @@
Sign In
+
-
-
-
-
Welcome
-
Sign in with your Microsoft account
+
+
+
Welcome
+
Sign in with your Microsoft account
-
+
-
+
-
-
Signed in
-
Name:
-
Email:
+
+
Signed in
+
Name:
+
Email:
-
-
+
+
@@ -157,11 +40,11 @@
}
function showAuthedUI(displayName, email) {
- loginBtn.style.display = 'none';
+ loginBtn.classList.add('hidden');
loginBtn.disabled = false;
loginBtn.textContent = 'Sign in with Microsoft';
- userInfo.style.display = 'block';
- continueBtn.style.display = 'block';
+ userInfo.classList.remove('hidden');
+ continueBtn.classList.remove('hidden');
document.getElementById('displayName').textContent = displayName || 'Unknown';
document.getElementById('email').textContent = email || 'Not available';
}
@@ -169,7 +52,7 @@
async function login() {
loginBtn.disabled = true;
loginBtn.textContent = 'Signing in...';
- errorEl.style.display = 'none';
+ errorEl.classList.add('hidden');
try {
const authData = await pb.collection('users').authWithOAuth2({
@@ -179,7 +62,7 @@
} catch (error) {
console.error('Login failed:', error);
errorEl.textContent = error.message || 'Authentication failed. Please try again.';
- errorEl.style.display = 'block';
+ errorEl.classList.remove('hidden');
loginBtn.disabled = false;
loginBtn.textContent = 'Sign in with Microsoft';
}
@@ -187,9 +70,9 @@
function logout() {
pb.authStore.clear();
- loginBtn.style.display = 'block';
- userInfo.style.display = 'none';
- continueBtn.style.display = 'none';
+ loginBtn.classList.remove('hidden');
+ userInfo.classList.add('hidden');
+ continueBtn.classList.add('hidden');
loginBtn.disabled = false;
loginBtn.textContent = 'Sign in with Microsoft';
}
diff --git a/images/apple.svg b/images/apple.svg
new file mode 100644
index 0000000..de4b0d1
--- /dev/null
+++ b/images/apple.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/images/google.svg b/images/google.svg
new file mode 100644
index 0000000..2eaf915
--- /dev/null
+++ b/images/google.svg
@@ -0,0 +1 @@
+
\ No newline at end of file