🛠️
Pro Handyman
Enter 4-digit code

Pro Handyman

Job Board

šŸ“‹Jobs
šŸ“øPhotos
šŸ”Inspect
āž•New Lead

šŸ” Multi-Point Walk-Thru

Check every area — find more work for the team

šŸ  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)
=4){if(p==='9110'){document.getElementById('pin-screen').style.display='none';sessionStorage.setItem('phr_pin_ok','1');}else{p='';var ds=document.querySelectorAll('#d0,#d1,#d2,#d3');for(var i=0;i0){p=p.slice(0,-1);upd();document.getElementById('err').textContent='';}} function upd(){for(var i=0;i<4;i++){var d=document.getElementById('d'+i);if(d){d.style.background=i0){p=p.slice(0,-1);upd();document.getElementById('err').textContent='';}} function upd(){for(var i=0;i<4;i++){var d=document.getElementById('d'+i);d.style.background=i tc.classList.remove('active')); document.getElementById('tab-' + tabName).classList.add('active'); if (tabName === 'jobs') renderJobs(); if (tabName === 'photos') renderPhotoGallery(); if (tabName === 'inspection') populateInspectionJobSelect(); } // ==================== JOBS ==================== function getTypeBadge(type) { const map = { 'appliance': {cls:'badge-appliance', label:'šŸ”Œ Appliance'}, 'bathroom': {cls:'badge-bathroom', label:'🚿 Bathroom'}, 'general': {cls:'badge-general', label:'šŸ”§ General'}, 'door': {cls:'badge-door', label:'🚪 Door & Entry'}, 'tv': {cls:'badge-tv', label:'šŸ“ŗ TV Mount'} }; return map[type] || map['general']; } function getStatusPill(status) { const map = { 'open': {cls:'status-open', label:'Open', dot:''}, 'accepted': {cls:'status-accepted', label:'Accepted', dot:''}, 'inprogress': {cls:'status-inprogress', label:'In Progress', dot:''}, 'completed': {cls:'status-completed', label:'Completed', dot:''} }; return map[status] || map['open']; } function formatDate(dateStr) { if (!dateStr) return ''; const d = new Date(dateStr + 'T12:00:00'); return d.toLocaleDateString('en-US', {weekday:'short', month:'short', day:'numeric'}); } function renderJobs() { const jobs = loadJobs(); const container = document.getElementById('job-list'); container.innerHTML = jobs.map(job => { const badge = getTypeBadge(job.type); const status = getStatusPill(job.status); const priceStr = job.price ? `$${job.price}` : 'TBD'; const phoneLink = job.phone ? `šŸ“ž Call` : ''; const mapsLink = job.mapsLink ? `šŸ“ Maps` : ''; const zillowLink = job.zillowLink ? `šŸ  Zillow` : ''; let actions = ''; if (job.status === 'open') { actions = `
${installers.map(i => `${i}`).join('')}
`; } else if (job.status === 'accepted') { actions = `
`; } else if (job.status === 'inprogress') { actions = `
`; } else if (job.status === 'completed') { const completionNotes = job.completionNotes || ''; const googleReviewLink = 'https://www.google.com/search?q=Pro+Handyman+Renovation+Boynton+Beach+FL#lrd=0x88d9e5e4e4f4f4f4:0x0,1,,,,'; const yelpReviewLink = 'https://www.yelp.com/write_review?biz_id=pro-handyman-renovation-boynton-beach'; actions = ` ${completionNotes ? `
šŸ“ Completion Notes:
${completionNotes}
` : ''} `; } const assignedHtml = job.assignedTo ? `
šŸ‘· ${job.assignedTo}
` : ''; const photos = loadPhotos(); const jobPhotos = photos[job.id] || {before:[], after:[]}; const beforeThumbs = jobPhotos.before.map(p => ``).join(''); const afterThumbs = jobPhotos.after.map(p => ``).join(''); return `
${badge.label}
${job.title}
šŸ‘¤ ${job.customer}
šŸ“… ${formatDate(job.date)}  ā€¢  ${job.address}
${priceStr}
${status.label}
${assignedHtml} ${actions}

šŸ“ø Before

${beforeThumbs}

šŸ“ø After

${afterThumbs}
`; }).join(''); } let currentPhotoTarget = {jobId: '', type: ''}; function setPhotoTarget(jobId, type) { currentPhotoTarget = {jobId, type}; } function uploadPhoto(input) { const file = input.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { const photos = loadPhotos(); const target = currentPhotoTarget; if (!photos[target.jobId]) photos[target.jobId] = {before:[], after:[]}; photos[target.jobId][target.type].push(e.target.result); savePhotos(photos); renderJobs(); showToast('šŸ“ø Photo saved!'); }; reader.readAsDataURL(file); input.value = ''; } function assignInstaller(jobId, name, el) { el.classList.toggle('selected'); const jobs = loadJobs(); const job = jobs.find(j => j.id === jobId); const selected = el.parentElement.querySelectorAll('.installer-chip.selected'); job.assignedTo = selected.length > 0 ? Array.from(selected).map(s => s.textContent).join(' & ') : null; saveJobs(jobs); } function acceptJob(jobId) { const jobs = loadJobs(); jobs.find(j => j.id === jobId).status = 'accepted'; saveJobs(jobs); renderJobs(); showToast('āœ… Job accepted!'); } function declineJob(jobId) { const jobs = loadJobs(); const job = jobs.find(j => j.id === jobId); job.status = 'open'; job.assignedTo = null; saveJobs(jobs); renderJobs(); showToast('Job declined'); } function startJob(jobId) { const jobs = loadJobs(); jobs.find(j => j.id === jobId).status = 'inprogress'; saveJobs(jobs); renderJobs(); showToast('šŸ”Ø Work started!'); } function completeJob(jobId) { const notesEl = document.getElementById('completion-notes-' + jobId); const completionNotes = notesEl ? notesEl.value.trim() : ''; const jobs = loadJobs(); const job = jobs.find(j => j.id === jobId); job.status = 'completed'; job.completionNotes = completionNotes; job.completedDate = new Date().toISOString(); saveJobs(jobs); renderJobs(); showToast('šŸŽ‰ Job completed!'); } // ==================== PHOTOS ==================== function renderPhotoGallery() { const jobs = loadJobs(); const photos = loadPhotos(); const container = document.getElementById('photo-gallery'); let html = ''; jobs.forEach(job => { const jp = photos[job.id] || {before:[], after:[]}; if (jp.before.length || jp.after.length) { html += `
${job.title} — ${job.customer}
${job.address}

šŸ“ø Before

${jp.before.map(p => ``).join('')}

šŸ“ø After

${jp.after.map(p => ``).join('')}
`; } }); if (!html) html = '
šŸ“ø

No photos yet. Take before/after
photos from the Jobs tab!

'; container.innerHTML = html; } // ==================== INSPECTION ==================== let inspectionData = {}; let inspectionPhotos = []; function setInspection(btn, status) { const parent = btn.parentElement; parent.querySelectorAll('.inspection-btn').forEach(b => b.classList.remove('good', 'needs-work')); btn.classList.add(status); const item = btn.closest('.inspection-item'); const label = item.querySelector('label').textContent; inspectionData[label] = status === 'needs-work' ? 'needs work' : 'good'; } function addInspectionPhoto(input) { const file = input.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { inspectionPhotos.push(e.target.result); document.getElementById('inspection-photos-preview').innerHTML += ``; showToast('šŸ“ø Photo added!'); }; reader.readAsDataURL(file); input.value = ''; } function populateInspectionJobSelect() { const jobs = loadJobs(); const select = document.getElementById('inspection-job-select'); select.innerHTML = '' + jobs.filter(j => j.status !== 'completed').map(j => `` ).join(''); } function toggleNewLeadFields() { const select = document.getElementById('inspection-job-select'); const fields = document.getElementById('new-lead-fields'); fields.style.display = select.value === '__new_lead__' ? 'block' : 'none'; } function submitInspection() { const jobId = document.getElementById('inspection-job-select').value; const notes = document.getElementById('inspection-notes').value; if (!jobId) { showToast('āš ļø Select a job first!'); return; } // Handle new lead vs existing job let jobTitle, customerName, customerPhone, customerEmail, customerAddress, customerSource, estimateItems, estimateParts, estimateLabor, estimateTotal; if (jobId === '__new_lead__') { customerName = document.getElementById('new-lead-name').value.trim(); customerPhone = document.getElementById('new-lead-phone').value.trim(); customerEmail = document.getElementById('new-lead-email').value.trim(); customerAddress = document.getElementById('new-lead-address').value.trim(); customerSource = document.getElementById('new-lead-source').value.trim(); estimateItems = document.getElementById('new-lead-estimate-items').value.trim(); estimateParts = document.getElementById('new-lead-estimate-parts').value.trim(); estimateLabor = document.getElementById('new-lead-estimate-labor').value.trim(); estimateTotal = document.getElementById('new-lead-estimate-total').value.trim(); if (!customerName) { showToast('āš ļø Enter customer name!'); return; } jobTitle = 'New Lead — Walk-Thru'; } else { const jobs = loadJobs(); const job = jobs.find(j => j.id === jobId); jobTitle = job.title; customerName = job.customer; customerPhone = job.phone || ''; customerEmail = job.email || ''; customerAddress = job.address; customerSource = 'Existing Job'; estimateItems = ''; estimateParts = ''; estimateLabor = ''; estimateTotal = ''; } const needsWork = Object.entries(inspectionData).filter(([k,v]) => v === 'needs work').map(([k]) => k); const goodItems = Object.entries(inspectionData).filter(([k,v]) => v === 'good').map(([k]) => k); const report = { jobId, jobTitle, customer: customerName, address: customerAddress, date: new Date().toISOString(), items: inspectionData, needsWork, notes, isNewLead: jobId === '__new_lead__', customerPhone, customerEmail, customerSource, estimateItems, estimateParts, estimateLabor, estimateTotal, photoCount: inspectionPhotos.length }; const inspections = JSON.parse(localStorage.getItem(INSPECTION_KEY) || '[]'); inspections.push(report); localStorage.setItem(INSPECTION_KEY, JSON.stringify(inspections)); // Build email report const inspectorName = prompt('Your name:', '') || 'Installer'; const dateStr = new Date().toLocaleDateString('en-US', {weekday:'long', month:'long', day:'numeric', year:'numeric', hour:'numeric', minute:'2-digit'}); let emailBody = `šŸ” WALK-THRU INSPECTION REPORT\n`; emailBody += `━━━━━━━━━━━━━━━━━━━━━━━━━━\n`; if (jobId === '__new_lead__') { emailBody += `šŸ†• NEW LEAD\n`; emailBody += `━━━━━━━━━━━━━━━━━━━━━━━━━━\n`; } emailBody += `Job: ${jobTitle}\n`; emailBody += `Customer: ${customerName}\n`; if (customerPhone) emailBody += `Phone: ${customerPhone}\n`; if (customerEmail) emailBody += `Email: ${customerEmail}\n`; emailBody += `Address: ${customerAddress}\n`; if (customerSource) emailBody += `Source: ${customerSource}\n`; if (estimateItems || estimateTotal) { emailBody += `\nšŸ’° ESTIMATE:\n`; if (estimateItems) emailBody += ` Work: ${estimateItems}\n`; if (estimateParts) emailBody += ` Parts: $${estimateParts}\n`; if (estimateLabor) emailBody += ` Labor: $${estimateLabor}\n`; if (estimateTotal) emailBody += ` TOTAL: $${estimateTotal}\n`; } emailBody += `Inspector: ${inspectorName}\n`; emailBody += `Date: ${dateStr}\n\n`; if (needsWork.length > 0) { emailBody += `āš ļø NEEDS WORK (${needsWork.length} items):\n`; needsWork.forEach(item => { emailBody += ` āŒ ${item}\n`; }); emailBody += `\n`; } if (goodItems.length > 0) { emailBody += `āœ… GOOD (${goodItems.length} items):\n`; goodItems.forEach(item => { emailBody += ` āœ“ ${item}\n`; }); emailBody += `\n`; } if (notes) { emailBody += `šŸ“ NOTES & ESTIMATES:\n${notes}\n\n`; } emailBody += `━━━━━━━━━━━━━━━━━━━━━━━━━━\n`; emailBody += `Pro Handyman Renovation\n`; emailBody += `Ben & Pierre\n`; emailBody += `561-316-4440 | 561-566-2121\n`; emailBody += `INFO@911URGENT.COM` const leadTag = jobId === '__new_lead__' ? 'šŸ†• NEW LEAD — ' : ''; const subject = `${leadTag}šŸ” Inspection: ${jobTitle} — ${customerName} (${needsWork.length > 0 ? needsWork.length + ' issues found' : 'All good'})`; // Send to both Ben and Pierre via mailto const mailtoLink = `mailto:ben@911urgent.com,drtallerie@yahoo.com,probenceo@gmail.com?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(emailBody)}`; window.location.href = mailtoLink; // Reset form inspectionData = {}; inspectionPhotos = []; document.getElementById('inspection-notes').value = ''; document.getElementById('inspection-photos-preview').innerHTML = ''; document.getElementById('new-lead-name').value = ''; document.getElementById('new-lead-phone').value = ''; document.getElementById('new-lead-email').value = ''; document.getElementById('new-lead-address').value = ''; document.getElementById('new-lead-source').value = ''; document.getElementById('new-lead-estimate-items').value = ''; document.getElementById('new-lead-estimate-parts').value = ''; document.getElementById('new-lead-estimate-labor').value = ''; document.getElementById('new-lead-estimate-total').value = ''; document.getElementById('new-lead-fields').style.display = 'none'; document.querySelectorAll('.inspection-btn').forEach(b => b.classList.remove('good', 'needs-work')); if (needsWork.length > 0) { showToast(`šŸ“§ Report sent — ${needsWork.length} items need work!`); } else { showToast('šŸ“§ Inspection report sent!'); } } // ==================== OVERLAY ==================== function showPhoto(src) { document.getElementById('overlay-img').src = src; document.getElementById('photo-overlay').classList.add('show'); } function closeOverlay() { document.getElementById('photo-overlay').classList.remove('show'); } // ==================== TOAST ==================== function showToast(msg) { const toast = document.getElementById('toast'); toast.textContent = msg; toast.classList.add('show'); setTimeout(() => toast.classList.remove('show'), 2500); } // ==================== NEW LEAD (CRM) ==================== function submitNewLead() { var name = document.getElementById('lead-name').value.trim(); var phone = document.getElementById('lead-phone').value.trim(); var email = document.getElementById('lead-email').value.trim(); var address = document.getElementById('lead-address').value.trim(); var city = document.getElementById('lead-city').value.trim(); var category = document.getElementById('lead-category').value; var source = document.getElementById('lead-source').value; var service = document.getElementById('lead-service').value.trim(); var price = document.getElementById('lead-price').value.trim(); var scheduled = document.getElementById('lead-scheduled').value.trim(); var notes = document.getElementById('lead-notes').value.trim(); if (!name) { showToast('āš ļø Customer name is required!'); return; } var btn = document.getElementById('lead-submit-btn'); var resultDiv = document.getElementById('lead-result'); btn.disabled = true; btn.textContent = 'ā³ Creating lead...'; resultDiv.style.display = 'none'; // Build notes with all details var crmNotes = ''; if (service) crmNotes += 'Service: ' + service + '\n'; if (price) crmNotes += 'Estimate: $' + price + '\n'; if (scheduled) crmNotes += 'Scheduled: ' + scheduled + '\n'; if (notes) crmNotes += 'Notes: ' + notes; // Parse city/state/zip var cityParts = city.split(','); var cityName = ''; var stateName = ''; var zipCode = ''; if (cityParts.length >= 2) { cityName = cityParts[0].trim(); var stateZip = cityParts[1].trim(); var szParts = stateZip.split(' '); stateName = szParts[0] || ''; zipCode = szParts[1] || ''; } else { cityName = city.trim(); } var payload = { pin: '9110', action: 'create_lead', name: name, phone: phone, email: email, address: address, city: cityName, state: stateName, zip: zipCode, category: category, source: source || 'Walk-in', service: service, notes: crmNotes }; fetch(API_URL, { method: 'POST', mode: 'no-cors', headers: {'Content-Type': 'text/plain'}, body: JSON.stringify(payload) }).then(function() { btn.disabled = false; btn.textContent = 'šŸ“¤ Create Lead in CRM'; resultDiv.style.display = 'block'; resultDiv.className = 'lead-success'; resultDiv.innerHTML = 'āœ… Lead submitted to CRM!
' + name + ' — ' + (category || 'General') + ''; // Clear form document.getElementById('lead-name').value = ''; document.getElementById('lead-phone').value = ''; document.getElementById('lead-email').value = ''; document.getElementById('lead-address').value = ''; document.getElementById('lead-city').value = ''; document.getElementById('lead-category').selectedIndex = 0; document.getElementById('lead-source').selectedIndex = 0; document.getElementById('lead-service').value = ''; document.getElementById('lead-price').value = ''; document.getElementById('lead-scheduled').value = ''; document.getElementById('lead-notes').value = ''; showToast('āœ… Lead sent to CRM!'); }).catch(function(err) { btn.disabled = false; btn.textContent = 'šŸ“¤ Create Lead in CRM'; resultDiv.style.display = 'block'; resultDiv.className = 'lead-error'; resultDiv.innerHTML = 'āŒ Error: ' + err.message + '
Try again or enter manually in CRM.'; showToast('āŒ Failed — try again'); }); } // ==================== INIT ==================== renderJobs();
Pro Handyman Renovation  ā€¢  Ben & Pierre
561-316-4440  |  561-566-2121  |  INFO@911URGENT.COM