export function postConstructionCleaningCalculator(form: FormTs) {
// Base rates by construction type
const constructionTypeRates: Record<string, number> = {
'new-build': 0.25, // per sq ft
'renovation': 0.20,
'remodel': 0.22,
'addition': 0.18,
'commercial': 0.30
};
// Cleaning phase multipliers
const phaseMultipliers: Record<string, number> = {
'rough': 0.6, // After framing, before drywall
'final': 1.0, // After all work complete
'touch-up': 0.3, // Quick final pass
'complete': 1.5 // All phases included
};
// Debris level adjustments
const debrisMultipliers: Record<string, number> = {
'light': 0.8,
'moderate': 1.0,
'heavy': 1.3,
'extreme': 1.6
};
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'Post-Construction Cleaning Quote',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b' }
});
});
form.addSpacer({ height: 20 });
// Project Details Section
const projectSection = form.addSubform('projectDetails', { title: '🏗️ Project Details' });
projectSection.addRow(row => {
row.addAddress('siteAddress', {
label: 'Construction Site Address',
placeholder: 'Enter site address...',
showMap: true,
showDistance: true,
referenceAddress: {
formattedAddress: 'Service Center, Denver, CO',
coordinates: { lat: 39.7392, lng: -104.9903 }
},
restrictToCountries: ['US', 'CA'],
distanceUnit: 'miles',
isRequired: true
});
});
projectSection.addRow(row => {
row.addTextPanel('travelZoneInfo', {
computedValue: () => {
const addressField = projectSection.address('siteAddress');
const miles = addressField?.distance();
if (miles == null) return '📍 Enter address to calculate travel fee';
if (miles <= 20) return '📍 Within service area - No travel fee';
if (miles <= 40) return '📍 Extended area - $100 travel fee';
if (miles <= 60) return '📍 Remote area - $200 travel fee';
return '📍 Long distance - $300+ travel fee';
},
customStyles: { 'font-size': '0.9rem', 'color': '#0369a1', 'background': '#e0f2fe', 'padding': '10px', 'border-radius': '6px' }
});
});
projectSection.addRow(row => {
row.addDropdown('constructionType', {
label: 'Construction Type',
options: [
{ id: 'new-build', name: 'New Construction' },
{ id: 'renovation', name: 'Renovation' },
{ id: 'remodel', name: 'Remodel' },
{ id: 'addition', name: 'Home Addition' },
{ id: 'commercial', name: 'Commercial Build-out' }
],
defaultValue: 'new-build',
isRequired: true
}, '1fr');
row.addDropdown('propertyType', {
label: 'Property Type',
options: [
{ id: 'residential', name: 'Residential Home' },
{ id: 'apartment', name: 'Apartment/Condo' },
{ id: 'townhouse', name: 'Townhouse' },
{ id: 'commercial-small', name: 'Small Commercial' },
{ id: 'commercial-large', name: 'Large Commercial' }
],
defaultValue: 'residential'
}, '1fr');
});
projectSection.addRow(row => {
row.addSlider('squareFootage', {
label: 'Total Square Footage',
min: 500,
max: 20000,
step: 100,
defaultValue: 2500,
showValue: true,
unit: 'sq ft'
}, '1fr');
row.addInteger('floors', {
label: 'Number of Floors',
min: 1,
max: 10,
defaultValue: 2
}, '1fr');
});
projectSection.addRow(row => {
row.addInteger('bathrooms', {
label: 'Bathrooms',
min: 1,
max: 15,
defaultValue: 3
}, '1fr');
row.addInteger('kitchens', {
label: 'Kitchens',
min: 0,
max: 5,
defaultValue: 1
}, '1fr');
});
// Cleaning Scope Section
const scopeSection = form.addSubform('cleaningScope', { title: '📋 Cleaning Scope' });
scopeSection.addRow(row => {
row.addRadioButton('cleaningPhase', {
label: 'Cleaning Phase',
options: [
{ id: 'rough', name: 'Rough Clean (Post-framing, before finishes) - 40% off' },
{ id: 'final', name: 'Final Clean (After all work complete)' },
{ id: 'touch-up', name: 'Touch-up Clean (Quick pass before move-in) - 70% off' },
{ id: 'complete', name: 'Complete Package (All phases included) +50%' }
],
defaultValue: 'final',
orientation: 'vertical',
isRequired: true
});
});
scopeSection.addRow(row => {
row.addRadioButton('debrisLevel', {
label: 'Debris/Dust Level',
options: [
{ id: 'light', name: 'Light - Minor dust, minimal debris' },
{ id: 'moderate', name: 'Moderate - Standard construction mess' },
{ id: 'heavy', name: 'Heavy - Significant dust and debris' },
{ id: 'extreme', name: 'Extreme - Major cleanup required' }
],
defaultValue: 'moderate',
orientation: 'vertical'
});
});
// Specific Areas Section
const areasSection = form.addSubform('specificAreas', { title: '🏠 Areas to Clean' });
areasSection.addRow(row => {
row.addCheckbox('windows', {
label: 'Windows (Interior & Exterior)',
defaultValue: true
}, '1fr');
row.addCheckbox('lightFixtures', {
label: 'Light Fixtures & Ceiling Fans',
defaultValue: true
}, '1fr');
});
areasSection.addRow(row => {
row.addCheckbox('cabinets', {
label: 'Cabinets (Inside & Outside)',
defaultValue: true
}, '1fr');
row.addCheckbox('appliances', {
label: 'Appliances (Inside & Outside)',
defaultValue: true
}, '1fr');
});
areasSection.addRow(row => {
row.addCheckbox('hvacVents', {
label: 'HVAC Vents & Returns',
defaultValue: true
}, '1fr');
row.addCheckbox('blindsShutters', {
label: 'Blinds/Shutters',
defaultValue: false
}, '1fr');
});
areasSection.addRow(row => {
row.addCheckbox('garage', {
label: 'Garage Cleaning',
defaultValue: false
}, '1fr');
row.addCheckbox('basement', {
label: 'Basement/Utility Areas',
defaultValue: false
}, '1fr');
});
// Additional Services Section
const addonsSection = form.addSubform('additionalServices', { title: '✨ Additional Services' });
addonsSection.addRow(row => {
row.addCheckbox('pressureWashing', {
label: 'Exterior Pressure Washing (+$200)',
defaultValue: false
}, '1fr');
row.addCheckbox('debrisRemoval', {
label: 'Debris Hauling/Removal (+$150)',
defaultValue: false
}, '1fr');
});
addonsSection.addRow(row => {
row.addCheckbox('carpetCleaning', {
label: 'Carpet Cleaning (+$0.25/sq ft)',
defaultValue: false
}, '1fr');
row.addCheckbox('floorPolishing', {
label: 'Floor Polishing/Sealing (+$0.30/sq ft)',
defaultValue: false
}, '1fr');
});
addonsSection.addRow(row => {
row.addCheckbox('stickerRemoval', {
label: 'Sticker/Label Removal (+$75)',
defaultValue: false
}, '1fr');
row.addCheckbox('touchupPaint', {
label: 'Touch-up Paint Cleaning (+$100)',
defaultValue: false
}, '1fr');
});
addonsSection.addRow(row => {
row.addSlider('carpetSqFt', {
label: 'Carpet Area (sq ft)',
min: 0,
max: 5000,
step: 100,
defaultValue: 500,
showValue: true,
unit: 'sq ft',
isVisible: () => addonsSection.checkbox('carpetCleaning')?.value() === true
}, '1fr');
row.addSlider('hardFloorSqFt', {
label: 'Hard Floor Area (sq ft)',
min: 0,
max: 5000,
step: 100,
defaultValue: 500,
showValue: true,
unit: 'sq ft',
isVisible: () => addonsSection.checkbox('floorPolishing')?.value() === true
}, '1fr');
});
// Timing Section
const timingSection = form.addSubform('timing', { title: '📅 Scheduling' });
timingSection.addRow(row => {
row.addDropdown('urgency', {
label: 'Timeline',
options: [
{ id: 'flexible', name: 'Flexible - Within 2 weeks' },
{ id: 'standard', name: 'Standard - Within 1 week' },
{ id: 'rush', name: 'Rush - Within 48 hours (+25%)' },
{ id: 'sameDay', name: 'Same Day - Emergency (+50%)' }
],
defaultValue: 'standard'
}, '1fr');
});
form.addSpacer({ height: 20, showLine: true, lineStyle: 'dashed' });
// Helper to calculate travel fee
const getTravelFee = () => {
const addressField = projectSection.address('siteAddress');
const miles = addressField?.distance();
if (miles == null || miles <= 20) return 0;
if (miles <= 40) return 100;
if (miles <= 60) return 200;
return 300 + Math.floor((miles - 60) / 10) * 30;
};
// Price Summary Section
const summarySection = form.addSubform('summary', { title: '💰 Your Quote', isCollapsible: false });
// Helper function for base price calculation
const calculateBasePrice = () => {
const sqft = projectSection.slider('squareFootage')?.value() || 2500;
const constructionType = projectSection.dropdown('constructionType')?.value() || 'new-build';
const rate = constructionTypeRates[constructionType] || 0.25;
return sqft * rate;
};
summarySection.addRow(row => {
row.addPriceDisplay('basePrice', {
label: 'Base Cleaning Cost',
computedValue: () => {
return Math.round(calculateBasePrice());
},
variant: 'default'
}, '1fr');
row.addPriceDisplay('phaseAdjustment', {
label: 'Cleaning Phase Adjustment',
computedValue: () => {
const base = calculateBasePrice();
const phase = scopeSection.radioButton('cleaningPhase')?.value() || 'final';
const multiplier = phaseMultipliers[phase] || 1;
return Math.round(base * (multiplier - 1));
},
variant: () => {
const phase = scopeSection.radioButton('cleaningPhase')?.value();
return phase === 'rough' || phase === 'touch-up' ? 'success' : 'default';
},
prefix: () => {
const phase = scopeSection.radioButton('cleaningPhase')?.value();
return phase === 'rough' || phase === 'touch-up' ? '' : '+';
}
}, '1fr');
});
summarySection.addRow(row => {
row.addPriceDisplay('debrisAdjustment', {
label: 'Debris Level Adjustment',
computedValue: () => {
const base = calculateBasePrice();
const phase = scopeSection.radioButton('cleaningPhase')?.value() || 'final';
const phaseMult = phaseMultipliers[phase] || 1;
const debris = scopeSection.radioButton('debrisLevel')?.value() || 'moderate';
const debrisMult = debrisMultipliers[debris] || 1;
return Math.round(base * phaseMult * (debrisMult - 1));
},
variant: () => {
const debris = scopeSection.radioButton('debrisLevel')?.value();
return debris === 'light' ? 'success' : 'default';
},
prefix: () => {
const debris = scopeSection.radioButton('debrisLevel')?.value();
return debris === 'light' ? '' : '+';
}
}, '1fr');
row.addPriceDisplay('roomsCharge', {
label: 'Bathrooms & Kitchens',
computedValue: () => {
const bathrooms = projectSection.integer('bathrooms')?.value() || 3;
const kitchens = projectSection.integer('kitchens')?.value() || 1;
const floors = projectSection.integer('floors')?.value() || 2;
return (bathrooms * 75) + (kitchens * 150) + ((floors - 1) * 50);
},
variant: 'default',
prefix: '+'
}, '1fr');
});
summarySection.addRow(row => {
row.addPriceDisplay('addonsTotal', {
label: 'Additional Services',
computedValue: () => {
let total = 0;
if (addonsSection.checkbox('pressureWashing')?.value()) total += 200;
if (addonsSection.checkbox('debrisRemoval')?.value()) total += 150;
if (addonsSection.checkbox('carpetCleaning')?.value()) {
const sqft = addonsSection.slider('carpetSqFt')?.value() || 0;
total += sqft * 0.25;
}
if (addonsSection.checkbox('floorPolishing')?.value()) {
const sqft = addonsSection.slider('hardFloorSqFt')?.value() || 0;
total += sqft * 0.30;
}
if (addonsSection.checkbox('stickerRemoval')?.value()) total += 75;
if (addonsSection.checkbox('touchupPaint')?.value()) total += 100;
return Math.round(total);
},
variant: 'default',
prefix: '+'
}, '1fr');
row.addPriceDisplay('urgencyCharge', {
label: 'Rush Fee',
computedValue: () => {
const urgency = timingSection.dropdown('urgency')?.value() || 'standard';
if (urgency === 'flexible' || urgency === 'standard') return 0;
const sqft = projectSection.slider('squareFootage')?.value() || 2500;
const constructionType = projectSection.dropdown('constructionType')?.value() || 'new-build';
const rate = constructionTypeRates[constructionType] || 0.25;
const phase = scopeSection.radioButton('cleaningPhase')?.value() || 'final';
const phaseMult = phaseMultipliers[phase] || 1;
const debris = scopeSection.radioButton('debrisLevel')?.value() || 'moderate';
const debrisMult = debrisMultipliers[debris] || 1;
const base = sqft * rate * phaseMult * debrisMult;
const rushMultiplier = urgency === 'rush' ? 0.25 : 0.50;
return Math.round(base * rushMultiplier);
},
variant: 'default',
prefix: '+',
isVisible: () => {
const urgency = timingSection.dropdown('urgency')?.value();
return urgency === 'rush' || urgency === 'sameDay';
}
}, '1fr');
});
summarySection.addRow(row => {
row.addPriceDisplay('travelFee', {
label: 'Travel Fee',
computedValue: () => getTravelFee(),
variant: 'default',
prefix: '+'
});
});
summarySection.addSpacer({ showLine: true, lineStyle: 'solid', lineColor: '#e2e8f0' });
summarySection.addRow(row => {
row.addPriceDisplay('totalPrice', {
label: 'Total Estimated Price',
computedValue: () => {
const sqft = projectSection.slider('squareFootage')?.value() || 2500;
const constructionType = projectSection.dropdown('constructionType')?.value() || 'new-build';
const rate = constructionTypeRates[constructionType] || 0.25;
const phase = scopeSection.radioButton('cleaningPhase')?.value() || 'final';
const phaseMult = phaseMultipliers[phase] || 1;
const debris = scopeSection.radioButton('debrisLevel')?.value() || 'moderate';
const debrisMult = debrisMultipliers[debris] || 1;
let total = sqft * rate * phaseMult * debrisMult;
// Rooms
const bathrooms = projectSection.integer('bathrooms')?.value() || 3;
const kitchens = projectSection.integer('kitchens')?.value() || 1;
const floors = projectSection.integer('floors')?.value() || 2;
total += (bathrooms * 75) + (kitchens * 150) + ((floors - 1) * 50);
// Add-ons
if (addonsSection.checkbox('pressureWashing')?.value()) total += 200;
if (addonsSection.checkbox('debrisRemoval')?.value()) total += 150;
if (addonsSection.checkbox('carpetCleaning')?.value()) {
const carpetSqft = addonsSection.slider('carpetSqFt')?.value() || 0;
total += carpetSqft * 0.25;
}
if (addonsSection.checkbox('floorPolishing')?.value()) {
const hardFloorSqft = addonsSection.slider('hardFloorSqFt')?.value() || 0;
total += hardFloorSqft * 0.30;
}
if (addonsSection.checkbox('stickerRemoval')?.value()) total += 75;
if (addonsSection.checkbox('touchupPaint')?.value()) total += 100;
// Urgency
const urgency = timingSection.dropdown('urgency')?.value() || 'standard';
if (urgency === 'rush') total *= 1.25;
if (urgency === 'sameDay') total *= 1.50;
// Add travel fee
total += getTravelFee();
return Math.round(total);
},
variant: 'large'
});
});
summarySection.addRow(row => {
row.addTextPanel('timeEstimate', {
computedValue: () => {
const sqft = projectSection.slider('squareFootage')?.value() || 2500;
const phase = scopeSection.radioButton('cleaningPhase')?.value() || 'final';
let hours = sqft / 400; // Base hours estimate
if (phase === 'complete') hours *= 2;
if (phase === 'touch-up') hours *= 0.4;
const minHours = Math.floor(hours);
const maxHours = Math.ceil(hours * 1.3);
return `Estimated time: ${minHours}-${maxHours} hours with a professional crew`;
},
customStyles: { 'font-size': '0.9rem', 'color': '#059669' }
});
});
const finalSection = form.addSubform('final', {
title: '🧾 Summary',
isCollapsible: false,
sticky: 'bottom'
});
finalSection.addRow(row => {
row.addPriceDisplay('totalPrice', {
label: 'Total Estimated Price',
computedValue: () => {
const sqft = projectSection.slider('squareFootage')?.value() || 2500;
const constructionType = projectSection.dropdown('constructionType')?.value() || 'new-build';
const rate = constructionTypeRates[constructionType] || 0.25;
const phase = scopeSection.radioButton('cleaningPhase')?.value() || 'final';
const phaseMult = phaseMultipliers[phase] || 1;
const debris = scopeSection.radioButton('debrisLevel')?.value() || 'moderate';
const debrisMult = debrisMultipliers[debris] || 1;
let total = sqft * rate * phaseMult * debrisMult;
const bathrooms = projectSection.integer('bathrooms')?.value() || 3;
const kitchens = projectSection.integer('kitchens')?.value() || 1;
const floors = projectSection.integer('floors')?.value() || 2;
total += (bathrooms * 75) + (kitchens * 150) + ((floors - 1) * 50);
if (addonsSection.checkbox('pressureWashing')?.value()) total += 200;
if (addonsSection.checkbox('debrisRemoval')?.value()) total += 150;
if (addonsSection.checkbox('carpetCleaning')?.value()) {
const carpetSqft = addonsSection.slider('carpetSqFt')?.value() || 0;
total += carpetSqft * 0.25;
}
if (addonsSection.checkbox('floorPolishing')?.value()) {
const hardFloorSqft = addonsSection.slider('hardFloorSqFt')?.value() || 0;
total += hardFloorSqft * 0.30;
}
if (addonsSection.checkbox('stickerRemoval')?.value()) total += 75;
if (addonsSection.checkbox('touchupPaint')?.value()) total += 100;
const urgency = timingSection.dropdown('urgency')?.value() || 'standard';
if (urgency === 'rush') total *= 1.25;
if (urgency === 'sameDay') total *= 1.50;
// Add travel fee
total += getTravelFee();
return Math.round(total);
},
variant: 'large'
});
});
finalSection.addRow(row => {
row.addTextPanel('disclaimer', {
computedValue: () => 'Final price based on actual site conditions.',
customStyles: { 'font-size': '0.85rem', 'color': '#64748b', 'font-style': 'italic' }
});
});
form.configureSubmitButton({
label: 'Schedule Assessment'
});
}