export function carpetCleaningCalculator(form: FormTs) {
// Base rates per sq ft by cleaning method
const methodRates: Record<string, number> = {
'steam': 0.25,
'dry': 0.30,
'shampoo': 0.20,
'encapsulation': 0.28,
'bonnet': 0.18
};
// Room type pricing (flat rate for standard room sizes)
const roomPrices: Record<string, number> = {
'bedroom': 40,
'livingRoom': 55,
'diningRoom': 45,
'hallway': 25,
'stairs': 35,
'closet': 15
};
// Carpet condition multipliers
const conditionMultipliers: Record<string, number> = {
'light': 0.9,
'normal': 1.0,
'heavy': 1.3,
'severe': 1.6
};
// Frequency discounts
const frequencyDiscounts: Record<string, number> = {
'one-time': 0,
'annually': 5,
'semi-annually': 10,
'quarterly': 15
};
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'Carpet Cleaning Quote',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b' }
});
});
form.addSpacer({ height: 20 });
// Location Section
const locationSection = form.addSubform('location', { title: '📍 Service Location' });
locationSection.addRow(row => {
row.addAddress('serviceAddress', {
label: 'Property Address',
placeholder: 'Enter your 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
});
});
locationSection.addRow(row => {
row.addTextPanel('travelZoneInfo', {
computedValue: () => {
const addressField = locationSection.address('serviceAddress');
const miles = addressField?.distance();
if (miles == null) return '📍 Enter address to calculate travel fee';
if (miles <= 15) return '📍 Within standard service area - No travel fee';
if (miles <= 30) return '📍 Extended area - $20 travel fee';
if (miles <= 50) return '📍 Remote area - $40 travel fee';
return '📍 Long distance - $60+ travel fee (please call)';
},
customStyles: { 'font-size': '0.9rem', 'color': '#0369a1', 'background': '#e0f2fe', 'padding': '10px', 'border-radius': '6px' }
});
});
// Pricing Method Selection
const methodSection = form.addSubform('pricingMethod', { title: '📊 Pricing Method' });
methodSection.addRow(row => {
row.addRadioButton('pricingType', {
label: 'Choose how to calculate your quote',
options: [
{ id: 'by-room', name: 'By Room - Best for residential (flat rate per room)' },
{ id: 'by-sqft', name: 'By Square Footage - Best for commercial/large areas' }
],
defaultValue: 'by-room',
orientation: 'vertical'
});
});
// Room-based Section
const roomSection = form.addSubform('roomDetails', {
title: '🛏️ Room Count',
isVisible: () => methodSection.radioButton('pricingType')?.value() === 'by-room'
});
roomSection.addRow(row => {
row.addInteger('bedrooms', {
label: 'Bedrooms ($40 each)',
min: 0,
max: 10,
defaultValue: 3
}, '1fr');
row.addInteger('livingRooms', {
label: 'Living/Family Rooms ($55 each)',
min: 0,
max: 5,
defaultValue: 1
}, '1fr');
});
roomSection.addRow(row => {
row.addInteger('diningRooms', {
label: 'Dining Rooms ($45 each)',
min: 0,
max: 5,
defaultValue: 1
}, '1fr');
row.addInteger('hallways', {
label: 'Hallways ($25 each)',
min: 0,
max: 10,
defaultValue: 2
}, '1fr');
});
roomSection.addRow(row => {
row.addInteger('stairFlights', {
label: 'Flights of Stairs ($35 each)',
min: 0,
max: 5,
defaultValue: 1
}, '1fr');
row.addInteger('closets', {
label: 'Walk-in Closets ($15 each)',
min: 0,
max: 10,
defaultValue: 0
}, '1fr');
});
// Square footage-based Section
const sqftSection = form.addSubform('sqftDetails', {
title: '📐 Area Details',
isVisible: () => methodSection.radioButton('pricingType')?.value() === 'by-sqft'
});
sqftSection.addRow(row => {
row.addSlider('totalSqFt', {
label: 'Total Carpet Area',
min: 100,
max: 10000,
step: 100,
defaultValue: 1500,
showValue: true,
unit: 'sq ft'
}, '1fr');
});
sqftSection.addRow(row => {
row.addDropdown('cleaningMethod', {
label: 'Cleaning Method',
options: [
{ id: 'steam', name: 'Steam/Hot Water Extraction ($0.25/sq ft) - Recommended' },
{ id: 'dry', name: 'Dry Cleaning ($0.30/sq ft) - Quick dry time' },
{ id: 'shampoo', name: 'Shampoo ($0.20/sq ft) - Budget option' },
{ id: 'encapsulation', name: 'Encapsulation ($0.28/sq ft) - Commercial grade' },
{ id: 'bonnet', name: 'Bonnet Cleaning ($0.18/sq ft) - Surface clean' }
],
defaultValue: 'steam'
}, '1fr');
});
// Carpet Condition Section
const conditionSection = form.addSubform('carpetCondition', { title: '🔍 Carpet Condition' });
conditionSection.addRow(row => {
row.addRadioButton('soilLevel', {
label: 'Current carpet condition',
options: [
{ id: 'light', name: 'Light Soiling - Recently cleaned, minimal traffic (-10%)' },
{ id: 'normal', name: 'Normal Soiling - Typical residential wear' },
{ id: 'heavy', name: 'Heavy Soiling - High traffic, visible stains (+30%)' },
{ id: 'severe', name: 'Severe Soiling - Not cleaned in years, pet damage (+60%)' }
],
defaultValue: 'normal',
orientation: 'vertical'
});
});
conditionSection.addRow(row => {
row.addCheckbox('hasPets', {
label: 'Pet household (adds pet treatment)',
defaultValue: false
}, '1fr');
row.addCheckbox('hasOdors', {
label: 'Odor issues present',
defaultValue: false
}, '1fr');
});
// Additional Services Section
const addonsSection = form.addSubform('addons', { title: '✨ Additional Services' });
addonsSection.addRow(row => {
row.addCheckbox('stainProtection', {
label: 'Stain Protection Treatment (+$0.10/sq ft)',
defaultValue: false
}, '1fr');
row.addCheckbox('deodorizing', {
label: 'Deodorizing Treatment (+$0.05/sq ft)',
defaultValue: false
}, '1fr');
});
addonsSection.addRow(row => {
row.addCheckbox('petTreatment', {
label: 'Pet Urine Treatment (+$25/area)',
defaultValue: false
}, '1fr');
row.addCheckbox('spotTreatment', {
label: 'Spot/Stain Treatment (+$15/spot)',
defaultValue: false
}, '1fr');
});
addonsSection.addRow(row => {
row.addCheckbox('furnitureMoving', {
label: 'Furniture Moving (+$30)',
defaultValue: false
}, '1fr');
row.addCheckbox('speedDry', {
label: 'Speed Dry Service (+$40)',
defaultValue: false
}, '1fr');
});
addonsSection.addRow(row => {
row.addInteger('petAreas', {
label: 'Number of Pet Areas',
min: 1,
max: 10,
defaultValue: 2,
isVisible: () => addonsSection.checkbox('petTreatment')?.value() === true
}, '1fr');
row.addInteger('spotCount', {
label: 'Number of Spots/Stains',
min: 1,
max: 20,
defaultValue: 3,
isVisible: () => addonsSection.checkbox('spotTreatment')?.value() === true
}, '1fr');
});
// Upholstery Add-on Section
const upholsterySection = form.addSubform('upholstery', { title: '🛋️ Upholstery Cleaning (Optional)' });
upholsterySection.addRow(row => {
row.addInteger('sofas', {
label: 'Sofas/Couches ($75 each)',
min: 0,
max: 10,
defaultValue: 0
}, '1fr');
row.addInteger('loveseats', {
label: 'Loveseats ($55 each)',
min: 0,
max: 10,
defaultValue: 0
}, '1fr');
});
upholsterySection.addRow(row => {
row.addInteger('chairs', {
label: 'Chairs/Recliners ($40 each)',
min: 0,
max: 20,
defaultValue: 0
}, '1fr');
row.addInteger('mattresses', {
label: 'Mattresses ($60 each)',
min: 0,
max: 10,
defaultValue: 0
}, '1fr');
});
// Service Frequency Section
const frequencySection = form.addSubform('frequency', { title: '📅 Service Frequency' });
frequencySection.addRow(row => {
row.addRadioButton('serviceFrequency', {
label: 'Schedule recurring service for discounts',
options: [
{ id: 'one-time', name: 'One-time service' },
{ id: 'annually', name: 'Annual service (-5%)' },
{ id: 'semi-annually', name: 'Every 6 months (-10%)' },
{ id: 'quarterly', name: 'Quarterly (-15%)' }
],
defaultValue: 'one-time',
orientation: 'vertical'
});
});
form.addSpacer({ height: 20, showLine: true, lineStyle: 'dashed' });
// Price Summary Section
const summarySection = form.addSubform('summary', { title: '💰 Your Quote', isCollapsible: false });
// Helper to calculate travel fee based on distance
const getTravelFee = () => {
const addressField = locationSection.address('serviceAddress');
const miles = addressField?.distance();
if (miles == null || miles <= 15) return 0;
if (miles <= 30) return 20;
if (miles <= 50) return 40;
return 60 + Math.floor((miles - 50) / 20) * 10; // +$10 per 20 miles beyond 50
};
const calculateBaseCarpetPrice = () => {
const pricingType = methodSection.radioButton('pricingType')?.value() || 'by-room';
if (pricingType === 'by-room') {
const bedrooms = roomSection.integer('bedrooms')?.value() || 0;
const living = roomSection.integer('livingRooms')?.value() || 0;
const dining = roomSection.integer('diningRooms')?.value() || 0;
const hallways = roomSection.integer('hallways')?.value() || 0;
const stairs = roomSection.integer('stairFlights')?.value() || 0;
const closets = roomSection.integer('closets')?.value() || 0;
return (bedrooms * (roomPrices['bedroom'] ?? 40)) +
(living * (roomPrices['livingRoom'] ?? 55)) +
(dining * (roomPrices['diningRoom'] ?? 45)) +
(hallways * (roomPrices['hallway'] ?? 25)) +
(stairs * (roomPrices['stairs'] ?? 35)) +
(closets * (roomPrices['closet'] ?? 15));
} else {
const sqft = sqftSection.slider('totalSqFt')?.value() || 1500;
const method = sqftSection.dropdown('cleaningMethod')?.value() || 'steam';
const rate = methodRates[method] || 0.25;
return sqft * rate;
}
};
const getEstimatedSqFt = () => {
const pricingType = methodSection.radioButton('pricingType')?.value() || 'by-room';
if (pricingType === 'by-sqft') {
return sqftSection.slider('totalSqFt')?.value() || 1500;
}
// Estimate sq ft from rooms
const bedrooms = roomSection.integer('bedrooms')?.value() || 0;
const living = roomSection.integer('livingRooms')?.value() || 0;
const dining = roomSection.integer('diningRooms')?.value() || 0;
const hallways = roomSection.integer('hallways')?.value() || 0;
const closets = roomSection.integer('closets')?.value() || 0;
return (bedrooms * 150) + (living * 300) + (dining * 200) + (hallways * 50) + (closets * 40);
};
summarySection.addRow(row => {
row.addPriceDisplay('basePrice', {
label: 'Carpet Cleaning',
computedValue: () => Math.round(calculateBaseCarpetPrice()),
variant: 'default'
}, '1fr');
row.addPriceDisplay('conditionAdjustment', {
label: 'Condition Adjustment',
computedValue: () => {
const base = calculateBaseCarpetPrice();
const condition = conditionSection.radioButton('soilLevel')?.value() || 'normal';
const multiplier = conditionMultipliers[condition] || 1;
return Math.round(base * (multiplier - 1));
},
variant: () => {
const condition = conditionSection.radioButton('soilLevel')?.value();
return condition === 'light' ? 'success' : 'default';
},
prefix: () => {
const condition = conditionSection.radioButton('soilLevel')?.value();
return condition === 'light' ? '' : '+';
}
}, '1fr');
});
summarySection.addRow(row => {
row.addPriceDisplay('addonsTotal', {
label: 'Additional Treatments',
computedValue: () => {
let total = 0;
const sqft = getEstimatedSqFt();
if (addonsSection.checkbox('stainProtection')?.value()) total += sqft * 0.10;
if (addonsSection.checkbox('deodorizing')?.value()) total += sqft * 0.05;
if (addonsSection.checkbox('petTreatment')?.value()) {
const areas = addonsSection.integer('petAreas')?.value() || 0;
total += areas * 25;
}
if (addonsSection.checkbox('spotTreatment')?.value()) {
const spots = addonsSection.integer('spotCount')?.value() || 0;
total += spots * 15;
}
if (addonsSection.checkbox('furnitureMoving')?.value()) total += 30;
if (addonsSection.checkbox('speedDry')?.value()) total += 40;
// Pet household surcharge
if (conditionSection.checkbox('hasPets')?.value()) total += 25;
if (conditionSection.checkbox('hasOdors')?.value()) total += 35;
return Math.round(total);
},
variant: 'default',
prefix: '+'
}, '1fr');
row.addPriceDisplay('travelFee', {
label: 'Travel Fee',
computedValue: () => getTravelFee(),
variant: 'default',
prefix: '+'
}, '1fr');
});
summarySection.addRow(row => {
row.addPriceDisplay('upholsteryTotal', {
label: 'Upholstery Cleaning',
computedValue: () => {
const sofas = upholsterySection.integer('sofas')?.value() || 0;
const loveseats = upholsterySection.integer('loveseats')?.value() || 0;
const chairs = upholsterySection.integer('chairs')?.value() || 0;
const mattresses = upholsterySection.integer('mattresses')?.value() || 0;
return (sofas * 75) + (loveseats * 55) + (chairs * 40) + (mattresses * 60);
},
variant: 'default',
prefix: '+',
isVisible: () => {
const sofas = upholsterySection.integer('sofas')?.value() || 0;
const loveseats = upholsterySection.integer('loveseats')?.value() || 0;
const chairs = upholsterySection.integer('chairs')?.value() || 0;
const mattresses = upholsterySection.integer('mattresses')?.value() || 0;
return (sofas + loveseats + chairs + mattresses) > 0;
}
}, '1fr');
});
summarySection.addRow(row => {
row.addPriceDisplay('frequencyDiscount', {
label: 'Recurring Service Discount',
computedValue: () => {
let total = calculateBaseCarpetPrice();
const condition = conditionSection.radioButton('soilLevel')?.value() || 'normal';
total *= conditionMultipliers[condition] || 1;
const frequency = frequencySection.radioButton('serviceFrequency')?.value() || 'one-time';
const discountPercent = frequencyDiscounts[frequency] || 0;
return -Math.round(total * discountPercent / 100);
},
variant: 'success',
isVisible: () => {
const frequency = frequencySection.radioButton('serviceFrequency')?.value();
return frequency !== 'one-time';
}
});
});
summarySection.addSpacer({ showLine: true, lineStyle: 'solid', lineColor: '#e2e8f0' });
summarySection.addRow(row => {
row.addPriceDisplay('totalPrice', {
label: 'Total Price',
computedValue: () => {
// Base carpet cleaning
let total = calculateBaseCarpetPrice();
// Condition adjustment
const condition = conditionSection.radioButton('soilLevel')?.value() || 'normal';
total *= conditionMultipliers[condition] || 1;
// Add-ons
const sqft = getEstimatedSqFt();
if (addonsSection.checkbox('stainProtection')?.value()) total += sqft * 0.10;
if (addonsSection.checkbox('deodorizing')?.value()) total += sqft * 0.05;
if (addonsSection.checkbox('petTreatment')?.value()) {
const areas = addonsSection.integer('petAreas')?.value() || 0;
total += areas * 25;
}
if (addonsSection.checkbox('spotTreatment')?.value()) {
const spots = addonsSection.integer('spotCount')?.value() || 0;
total += spots * 15;
}
if (addonsSection.checkbox('furnitureMoving')?.value()) total += 30;
if (addonsSection.checkbox('speedDry')?.value()) total += 40;
if (conditionSection.checkbox('hasPets')?.value()) total += 25;
if (conditionSection.checkbox('hasOdors')?.value()) total += 35;
// Upholstery
const sofas = upholsterySection.integer('sofas')?.value() || 0;
const loveseats = upholsterySection.integer('loveseats')?.value() || 0;
const chairs = upholsterySection.integer('chairs')?.value() || 0;
const mattresses = upholsterySection.integer('mattresses')?.value() || 0;
total += (sofas * 75) + (loveseats * 55) + (chairs * 40) + (mattresses * 60);
// Frequency discount
const frequency = frequencySection.radioButton('serviceFrequency')?.value() || 'one-time';
const discountPercent = frequencyDiscounts[frequency] || 0;
total *= (1 - discountPercent / 100);
// Add travel fee
total += getTravelFee();
return Math.round(Math.max(total, 99)); // Minimum charge
},
variant: 'large'
});
});
summarySection.addRow(row => {
row.addTextPanel('dryTime', {
computedValue: () => {
const pricingType = methodSection.radioButton('pricingType')?.value();
if (pricingType === 'by-sqft') {
const method = sqftSection.dropdown('cleaningMethod')?.value() || 'steam';
const dryTimes: Record<string, string> = {
'steam': 'Dry time: 6-12 hours',
'dry': 'Dry time: 1-2 hours',
'shampoo': 'Dry time: 8-12 hours',
'encapsulation': 'Dry time: 2-4 hours',
'bonnet': 'Dry time: 2-4 hours'
};
return dryTimes[method] || 'Dry time: 6-12 hours';
}
return 'Dry time: 6-12 hours (steam cleaning)';
},
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 Price',
computedValue: () => {
let total = calculateBaseCarpetPrice();
const condition = conditionSection.radioButton('soilLevel')?.value() || 'normal';
total *= conditionMultipliers[condition] || 1;
const sqft = getEstimatedSqFt();
if (addonsSection.checkbox('stainProtection')?.value()) total += sqft * 0.10;
if (addonsSection.checkbox('deodorizing')?.value()) total += sqft * 0.05;
if (addonsSection.checkbox('petTreatment')?.value()) {
const areas = addonsSection.integer('petAreas')?.value() || 0;
total += areas * 25;
}
if (addonsSection.checkbox('spotTreatment')?.value()) {
const spots = addonsSection.integer('spotCount')?.value() || 0;
total += spots * 15;
}
if (addonsSection.checkbox('furnitureMoving')?.value()) total += 30;
if (addonsSection.checkbox('speedDry')?.value()) total += 40;
if (conditionSection.checkbox('hasPets')?.value()) total += 25;
if (conditionSection.checkbox('hasOdors')?.value()) total += 35;
const sofas = upholsterySection.integer('sofas')?.value() || 0;
const loveseats = upholsterySection.integer('loveseats')?.value() || 0;
const chairs = upholsterySection.integer('chairs')?.value() || 0;
const mattresses = upholsterySection.integer('mattresses')?.value() || 0;
total += (sofas * 75) + (loveseats * 55) + (chairs * 40) + (mattresses * 60);
const frequency = frequencySection.radioButton('serviceFrequency')?.value() || 'one-time';
const discountPercent = frequencyDiscounts[frequency] || 0;
total *= (1 - discountPercent / 100);
// Add travel fee
total += getTravelFee();
return Math.round(Math.max(total, 99));
},
variant: 'large'
});
});
finalSection.addRow(row => {
row.addTextPanel('disclaimer', {
computedValue: () => 'Minimum service charge: $99.',
customStyles: { 'font-size': '0.85rem', 'color': '#64748b', 'font-style': 'italic' }
});
});
form.configureSubmitButton({
label: 'Book Appointment'
});
}