Job Board / Tablero de Trabajos
Check every area β find more work for the team / Revise cada Γ‘rea β encuentre mΓ‘s trabajo
Fill out the customer info and we'll create the lead directly in CRMBOOST.
// ==================== PIPELINE TAB LOGIC ==================== var PIPELINE_PHASES = ['leads','contacted','scheduled','active','done','followup']; var PIPELINE_LABELS = {leads:'Lead In',contacted:'Contacted',scheduled:'Scheduled',active:'In Progress',done:'Completed',followup:'Follow-Up'}; var PIPELINE_ICONS = {leads:'π₯',contacted:'π',scheduled:'π ',active:'π¨',done:'β ',followup:'π¬'}; var NEXT_PHASE = {leads:'contacted',contacted:'scheduled',scheduled:'active',active:'done',done:'followup'}; // Map existing job status to pipeline phase function statusToPhase(status) { var map = { 'open':'leads', 'new':'leads', 'lead':'leads', 'contacted':'contacted', 'reached':'contacted', 'accepted':'scheduled', 'scheduled':'scheduled', 'confirmed':'scheduled', 'inprogress':'active', 'in-progress':'active', 'working':'active', 'completed':'done', 'complete':'done', 'done':'done', 'followup':'followup', 'follow-up':'followup', 'followed-up':'followup' }; var key = (status||'').toLowerCase().replace(/[\s_-]/g,''); return map[key] || 'leads'; } function sourceClass(source) { var s = (source||'').toLowerCase(); if (s.indexOf('proreferral')>=0||s.indexOf('home depot')>=0||s.indexOf('pro referral')>=0) return 'proreferral'; if (s.indexOf('realtor')>=0) return 'realtor'; if (s.indexOf('investor')>=0||s.indexOf('flipper')>=0) return 'investor'; if (s.indexOf('accountant')>=0||s.indexOf('cpa')>=0) return 'accountant'; if (s.indexOf('painter')>=0) return 'painter'; if (s.indexOf('contractor')>=0) return 'contractor'; if (s.indexOf('property manager')>=0) return 'propertymanager'; if (s.indexOf('insurance')>=0) return 'insurance'; if (s.indexOf('social media')>=0||s.indexOf('instagram')>=0||s.indexOf('facebook')>=0||s.indexOf('tiktok')>=0) return 'social'; if (s.indexOf('cold outreach')>=0) return 'coldoutreach'; if (s.indexOf('referral')>=0) return 'referral'; if (s.indexOf('google')>=0) return 'google'; if (s.indexOf('website')>=0) return 'website'; if (s.indexOf('yelp')>=0) return 'yelp'; if (s.indexOf('repeat')>=0) return 'repeat'; if (s.indexOf('walk')>=0) return 'walkin'; return 'other'; } function formatSource(source) { var s = (source||'other'); var map = {'ProReferral':'πΆ ProReferral','Referral':'π€ Referral','Google':'π Google','Website':'π Website','Yelp':'β Yelp','Repeat Customer':'π Repeat','Walk-in':'πΆ Walk-in','Walk-up':'πΆ Walk-in','Realtor':'π Realtor','Investor':'πΌ Investor','Accountant':'π Accountant','Painter':'π¨ Painter','Contractor':'ποΈ Contractor','Property Manager':'π’ Prop Mgr','Insurance':'π‘οΈ Insurance','Social Media':'π± Social','Cold Outreach':'π§ Cold'}; return map[s] || 'π ' + s; } function renderPipeline() { var jobs = loadJobs(); var phases = {}; PIPELINE_PHASES.forEach(function(p){ phases[p] = []; }); var totalRevenue = 0; var completedRevenue = 0; jobs.forEach(function(job) { var phase = job.pipelinePhase || statusToPhase(job.status); if (PIPELINE_PHASES.indexOf(phase) < 0) phase = 'leads'; job._phase = phase; phases[phase].push(job); var price = parseFloat(job.price) || 0; if (price > 0) totalRevenue += price; if (phase === 'done' || phase === 'followup') completedRevenue += price; }); // Stats var statsEl = document.getElementById('pipeline-stats'); if (statsEl) { var html = ''; PIPELINE_PHASES.forEach(function(p) { html += '
'; }); html += '
'; statsEl.innerHTML = html; } // Columns PIPELINE_PHASES.forEach(function(phase) { var body = document.getElementById('body-' + phase); var countEl = document.getElementById('count-' + phase); if (!body) return; var phaseJobs = phases[phase]; if (countEl) countEl.textContent = phaseJobs.length; if (!phaseJobs.length) { body.innerHTML = '
'; return; } // Sort: urgent first, then by date phaseJobs.sort(function(a, b) { var aU = (a.notes||'').toLowerCase().indexOf('urgent') >= 0 || (a.title||'').toLowerCase().indexOf('urgent') >= 0 ? 0 : 1; var bU = (b.notes||'').toLowerCase().indexOf('urgent') >= 0 || (b.title||'').toLowerCase().indexOf('urgent') >= 0 ? 0 : 1; if (aU !== bU) return aU - bU; return (a.date||'').localeCompare(b.date||''); }); var html = ''; phaseJobs.forEach(function(job) { var isUrgent = (job.notes||'').toLowerCase().indexOf('urgent') >= 0 || (job.title||'').toLowerCase().indexOf('urgent') >= 0 || (job.date && isTodayOrTomorrow(job.date)); var src = job.source || 'Other'; var srcCls = sourceClass(src); var price = job.price ? '$' + (typeof job.price === 'number' ? job.price.toLocaleString() : job.price) : ''; var customer = job.customer || job.title || 'Unknown'; var title = job.title || job.type || ''; var dateStr = job.date ? formatDateShort(job.date) : ''; html += '
'; }); body.innerHTML = html; }); } function advancePipeline(jobId, newPhase) { var jobs = loadJobs(); for (var i = 0; i < jobs.length; i++) { if (jobs[i].id === jobId) { jobs[i].pipelinePhase = newPhase; // Also update status for backwards compat var statusMap = {leads:'open',contacted:'contacted',scheduled:'accepted',active:'inprogress',done:'completed',followup:'completed'}; if (statusMap[newPhase]) jobs[i].status = statusMap[newPhase]; break; } } saveJobs(jobs); syncToSheet(); renderPipeline(); showToast('β Moved to ' + PIPELINE_LABELS[newPhase]); } function isTodayOrTomorrow(dateStr) { if (!dateStr) return false; var d = new Date(dateStr); var now = new Date(); var tomorrow = new Date(now); tomorrow.setDate(tomorrow.getDate() + 1); return d.toDateString() === now.toDateString() || d.toDateString() === tomorrow.toDateString(); } function formatDateShort(dateStr) { if (!dateStr) return ''; var d = new Date(dateStr + 'T12:00:00'); var days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; var months = ['1/','2/','3/','4/','5/','6/','7/','8/','9/','10/','11/','12/']; return days[d.getDay()] + ' ' + months[d.getMonth()] + d.getDate(); } function escHtml(str) { var div = document.createElement('div'); div.appendChild(document.createTextNode(str)); return div.innerHTML; } // Register the pipeline tab var originalSwitchTab = switchTab; switchTab = function(tabName) { if (tabName === 'pipeline') { var tabs = document.querySelectorAll('.tab'); tabs.forEach(function(t){t.classList.remove('active');}); var contents = document.querySelectorAll('.tab-content'); contents.forEach(function(c){c.classList.remove('active');}); // Find or create pipeline tab button var pipeTab = document.querySelector('[data-tab="pipeline"]'); if (pipeTab) pipeTab.classList.add('active'); document.getElementById('tab-pipeline').classList.add('active'); renderPipeline(); } else { originalSwitchTab(tabName); } };