š 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;i
0){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 `
${mapsLink}${zillowLink}${phoneLink}
${status.label}
${assignedHtml}
${actions}
`;
}).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