🛠️
Pro Handyman
Enter 4-digit code to access Job Board

Pro Handyman

Job Board / Tablero de Trabajos

πŸ“… Syncing calendar...
πŸ“‹Jobs / Trabajos
πŸ“ΈPhotos / Fotos
πŸ”Inspect / InspecciΓ³n
βž•New Lead / Nuevo
πŸ”₯Pipeline
⚠️ 0 photos pending upload
πŸ“Έ Photo Gallery

πŸ” Pro Walk-Through / InspecciΓ³n

Check every area β€” find more work for the team / Revise cada Γ‘rea β€” encuentre mΓ‘s trabajo

🏠 Exterior

πŸšͺ Interior

πŸ”Œ Kitchen

🚿 Bathroom

πŸ”§ Other Observations

πŸ“ Additional Notes & Estimates

βž• New Lead β€” Enter in CRM

Fill out the customer info and we'll create the lead directly in CRMBOOST.

πŸ’° Estimate Details (Optional)
πŸ“Έ Site Photos β€” snap before photos or damage

πŸ“‹ Job Lifecycle

1 Lead In
β†’
2 Contacted
β†’
3 Scheduled
β†’
4 In Progress
β†’
5 Completed
β†’
6 Follow-Up

πŸ”₯ Pipeline

πŸ“₯Lead In0
πŸ“žContacted0
πŸ“…Scheduled0
πŸ”¨In Progress0
βœ…Completed0
πŸ’¬Follow-Up0
Pro Handyman Renovation  β€’  Ben & Pierre
561-316-4440  |  561-566-2121  |  INFO@911URGENT.COM
// ==================== 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 += '
' + phases[p].length + ' ' + PIPELINE_LABELS[p] + '
'; }); html += '
πŸ’° $' + completedRevenue.toLocaleString() + ' closed
'; 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 = '
' + PIPELINE_ICONS[phase] + '
No ' + PIPELINE_LABELS[phase].toLowerCase() + ' jobs
'; 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 += '
'; html += '
' + formatSource(src) + '
'; html += '
' + escHtml(customer) + '
'; html += '
' + escHtml(title) + '
'; html += '
'; if (price) html += '' + price + ''; if (dateStr) html += 'πŸ“… ' + dateStr + ''; if (isUrgent) html += '⚑ URGENT'; html += '
'; // Action buttons based on phase html += '
'; if (phase === 'leads') { html += ''; html += ''; } else if (phase === 'contacted') { html += ''; } else if (phase === 'scheduled') { html += ''; } else if (phase === 'active') { html += ''; } else if (phase === 'done') { html += ''; } html += '
'; 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); } };