Cleaning Bid Calculator

Cleaning Bid Calculator: Professional Bidding Tool :root { –primary-color: #004a99; –success-color: #28a745; –background-color: #f8f9fa; –text-color: #333; –secondary-text-color: #666; –border-color: #ddd; –card-background: #fff; –shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: var(–background-color); color: var(–text-color); line-height: 1.6; margin: 0; padding: 0; display: flex; flex-direction: column; align-items: center; padding-top: 20px; padding-bottom: 40px; } .container { max-width: 960px; width: 100%; margin: 0 auto; padding: 0 15px; box-sizing: border-box; } header { background-color: var(–primary-color); color: white; padding: 20px 0; text-align: center; width: 100%; box-shadow: var(–shadow); margin-bottom: 30px; } header h1 { margin: 0; font-size: 2.5em; font-weight: 700; } .subtitle { font-size: 1.1em; opacity: 0.9; } .calculator-wrapper { background-color: var(–card-background); padding: 30px; border-radius: 8px; box-shadow: var(–shadow); margin-bottom: 40px; border: 1px solid var(–border-color); } .calculator-wrapper h2 { color: var(–primary-color); text-align: center; margin-top: 0; margin-bottom: 25px; font-size: 2em; } .loan-calc-container { display: flex; flex-direction: column; gap: 20px; } .input-group { display: flex; flex-direction: column; gap: 8px; } .input-group label { font-weight: 600; color: var(–primary-color); font-size: 0.95em; } .input-group input[type="number"], .input-group input[type="text"], .input-group select { padding: 12px 15px; border: 1px solid var(–border-color); border-radius: 5px; font-size: 1em; box-sizing: border-box; transition: border-color 0.3s ease; } .input-group input[type="number"]:focus, .input-group input[type="text"]:focus, .input-group select:focus { outline: none; border-color: var(–primary-color); box-shadow: 0 0 0 2px rgba(0, 74, 153, 0.2); } .input-group .helper-text { font-size: 0.85em; color: var(–secondary-text-color); } .input-group .error-message { color: #dc3545; font-size: 0.85em; margin-top: 5px; min-height: 1.2em; /* Prevent layout shift */ } .button-group { display: flex; gap: 15px; margin-top: 25px; flex-wrap: wrap; /* Allow wrapping on smaller screens */ } .button-group button { padding: 12px 25px; border: none; border-radius: 5px; cursor: pointer; font-size: 1em; font-weight: 600; transition: background-color 0.3s ease, transform 0.2s ease; flex: 1; /* Distribute space */ min-width: 150px; /* Minimum width before wrapping */ } .button-group .calculate-btn { background-color: var(–primary-color); color: white; } .button-group .calculate-btn:hover { background-color: #003366; } .button-group .reset-btn { background-color: var(–secondary-text-color); color: white; } .button-group .reset-btn:hover { background-color: #555; } .button-group .copy-btn { background-color: var(–success-color); color: white; } .button-group .copy-btn:hover { background-color: #218838; } .button-group button:active { transform: translateY(1px); } .results-wrapper { background-color: var(–card-background); padding: 30px; border-radius: 8px; box-shadow: var(–shadow); margin-top: 30px; border: 1px solid var(–border-color); text-align: center; } .results-wrapper h2 { color: var(–primary-color); margin-top: 0; margin-bottom: 25px; font-size: 2em; } .main-result-container { background-color: var(–success-color); color: white; padding: 20px; border-radius: 5px; margin-bottom: 25px; box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1); } .main-result-container .label { font-size: 1.1em; font-weight: 600; display: block; margin-bottom: 10px; } .main-result-container .value { font-size: 2.5em; font-weight: 700; } .intermediate-results { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 20px; margin-bottom: 25px; text-align: left; } .intermediate-results .result-item { background-color: var(–background-color); padding: 15px; border-radius: 5px; border: 1px solid var(–border-color); text-align: center; } .intermediate-results .result-item .label { font-size: 0.9em; color: var(–secondary-text-color); margin-bottom: 8px; display: block; font-weight: 600; } .intermediate-results .result-item .value { font-size: 1.6em; font-weight: 700; color: var(–primary-color); } .formula-explanation { font-size: 0.9em; color: var(–secondary-text-color); margin-top: 15px; padding-top: 15px; border-top: 1px dashed var(–border-color); } .chart-container { margin-top: 40px; background-color: var(–card-background); padding: 30px; border-radius: 8px; box-shadow: var(–shadow); border: 1px solid var(–border-color); text-align: center; } .chart-container h3 { color: var(–primary-color); margin-top: 0; margin-bottom: 20px; font-size: 1.8em; } canvas { max-width: 100%; height: auto !important; /* Ensure canvas scales */ } .chart-caption { font-size: 0.9em; color: var(–secondary-text-color); margin-top: 10px; } .table-container { margin-top: 40px; background-color: var(–card-background); padding: 30px; border-radius: 8px; box-shadow: var(–shadow); border: 1px solid var(–border-color); overflow-x: auto; /* For responsiveness */ } .table-container h3 { color: var(–primary-color); margin-top: 0; margin-bottom: 20px; font-size: 1.8em; } table { width: 100%; border-collapse: collapse; margin-top: 20px; } th, td { padding: 12px 15px; text-align: left; border: 1px solid var(–border-color); } thead { background-color: var(–primary-color); color: white; } th { font-weight: 600; } tbody tr:nth-child(even) { background-color: #f2f2f2; } .table-caption { font-size: 0.9em; color: var(–secondary-text-color); margin-top: 10px; caption-side: bottom; } main { width: 100%; } section { margin-bottom: 40px; background-color: var(–card-background); padding: 30px; border-radius: 8px; box-shadow: var(–shadow); border: 1px solid var(–border-color); } section h2 { color: var(–primary-color); font-size: 2em; margin-top: 0; margin-bottom: 20px; border-bottom: 2px solid var(–primary-color); padding-bottom: 10px; } section h3 { color: var(–primary-color); font-size: 1.6em; margin-top: 25px; margin-bottom: 15px; } section p, section ul, section ol { margin-bottom: 15px; color: var(–text-color); } section ul, section ol { padding-left: 25px; } section li { margin-bottom: 8px; } .faq-item { margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px dashed var(–border-color); } .faq-item:last-child { border-bottom: none; } .faq-item .question { font-weight: 600; color: var(–primary-color); cursor: pointer; display: flex; justify-content: space-between; align-items: center; } .faq-item .question::after { content: '+'; font-size: 1.2em; margin-left: 10px; } .faq-item.open .question::after { content: '-'; } .faq-item .answer { max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out; margin-top: 10px; font-size: 0.95em; color: var(–secondary-text-color); } .faq-item.open .answer { max-height: 200px; /* Adjust as needed */ } .internal-links { list-style: none; padding: 0; } .internal-links li { margin-bottom: 15px; } .internal-links a { color: var(–primary-color); text-decoration: none; font-weight: 600; } .internal-links a:hover { text-decoration: underline; } .internal-links span { display: block; font-size: 0.9em; color: var(–secondary-text-color); margin-top: 5px; } footer { text-align: center; margin-top: 40px; padding: 20px; font-size: 0.9em; color: var(–secondary-text-color); width: 100%; } @media (max-width: 768px) { header h1 { font-size: 2em; } .results-wrapper, .calculator-wrapper, section { padding: 20px; } .button-group button { flex: none; /* Allow buttons to take their own width */ width: 100%; /* Stack buttons */ } .intermediate-results { grid-template-columns: 1fr; } }

Cleaning Bid Calculator

Accurately Estimate Your Cleaning Service Costs and Profitability

Calculate Your Cleaning Bid

Enter the total square footage of the space to be cleaned.
One-Time Weekly (4x per month) Bi-Weekly (2x per month) Twice Weekly (8x per month) Three Times Weekly (12x per month) Daily (Approx. 30x per month)
Select how often cleaning services will be performed.
Your desired earnings per hour for each cleaner.
Estimate the total hours a cleaner will spend per visit. (e.g., 3 hours for 1 cleaner or 1.5 hours for 2 cleaners).
Average cost of cleaning supplies per square foot.
Percentage of total costs for indirect expenses (rent, insurance, admin).
Your target profit as a percentage of the total bid.

Your Cleaning Bid Summary

Estimated Bid Price Per Visit $0.00
Labor Cost Per Visit $0.00
Supply Cost Per Visit $0.00
Total Operating Cost Per Visit $0.00
Monthly/Frequency Bid $0.00
How the Bid is Calculated:

1. Labor Cost: (Cleaner Hours per Visit) x (Target Hourly Rate)
2. Supply Cost: (Total Area) x (Supply Cost per Sq Ft)
3. Total Operating Cost: Labor Cost + Supply Cost
4. Overhead Cost: Total Operating Cost x (Overhead Percentage / 100)
5. Cost Base for Profit: Total Operating Cost + Overhead Cost
6. Profit Amount: Cost Base for Profit x (Desired Profit Margin / 100)
7. Estimated Bid Price: Cost Base for Profit + Profit Amount

The Monthly/Frequency Bid is calculated by multiplying the Estimated Bid Price Per Visit by the number of cleaning visits per month based on your selected frequency.

Cost Breakdown Per Visit

This chart visualizes the proportion of Labor Costs, Supply Costs, Overhead, and Profit within your Estimated Bid Price Per Visit.

Detailed Cost Breakdown Per Visit

Component Cost Per Visit Percentage of Bid
A detailed breakdown of the costs contributing to your cleaning bid per visit.

What is a Cleaning Bid Calculator?

A cleaning bid calculator is an essential financial tool designed for cleaning businesses, from small independent cleaners to large commercial cleaning companies. Its primary purpose is to help service providers accurately determine the price to quote for cleaning services. This calculator takes various operational costs and desired profit margins into account to generate a competitive yet profitable bid price. Understanding and using a cleaning bid calculator effectively is crucial for financial stability and growth in the cleaning industry.

Who Should Use It?

  • Residential Cleaning Services: Professionals cleaning homes, apartments, and condos.
  • Commercial Cleaning Companies: Businesses offering janitorial services for offices, retail spaces, and industrial facilities.
  • Specialty Cleaning Providers: Services focused on areas like post-construction cleanup, move-in/move-out cleaning, or deep cleaning.
  • Freelance Cleaners: Individuals offering cleaning services directly to clients.

Common Misconceptions:

  • "Just Multiply Hours by Rate": Many new cleaners simply multiply estimated hours by an hourly rate, forgetting crucial elements like supplies, overhead, and profit. A proper cleaning bid calculator ensures all costs are covered.
  • "Lowest Bid Wins": While price is a factor, clients also value reliability, quality, and professionalism. Bidding too low can lead to burnout, poor service, and financial unsustainability. This tool helps find a balance.
  • "Estimating is Guesswork": Accurate bidding requires data and a structured approach. A cleaning bid calculator transforms estimation from guesswork into a data-driven process.

Cleaning Bid Calculator Formula and Mathematical Explanation

The core of a cleaning bid calculator revolves around systematically accounting for all expenses and adding a desired profit. The formula aims to ensure that every job is not only covered cost-wise but also contributes to the business's overall profitability.

Step-by-Step Derivation:

  1. Labor Cost Per Visit: This is the direct cost of paying your cleaning staff for the time spent at the client's location.
    Formula: `Cleaner Hours per Visit * Target Hourly Rate`
  2. Supply Cost Per Visit: The cost of consumables (cleaning solutions, cloths, gloves, etc.) used during a single cleaning session.
    Formula: `Total Area (sq ft) * Supply Cost per Sq Ft`
  3. Total Operating Cost Per Visit: The sum of direct labor and supply expenses for one cleaning session.
    Formula: `Labor Cost Per Visit + Supply Cost Per Visit`
  4. Overhead Cost Per Visit: A portion of indirect business expenses (rent, insurance, marketing, administrative salaries, equipment depreciation) allocated to this specific cleaning job. This is often calculated as a percentage of the operating costs.
    Formula: `Total Operating Cost Per Visit * (Overhead Percentage / 100)`
  5. Cost Base for Profit: The total cost incurred before adding profit. This includes direct operating costs and allocated overhead.
    Formula: `Total Operating Cost Per Visit + Overhead Cost Per Visit`
  6. Profit Amount Per Visit: The amount of money you aim to earn as profit for the service, calculated as a percentage of the total costs.
    Formula: `Cost Base for Profit * (Desired Profit Margin / 100)`
  7. Estimated Bid Price Per Visit: The final price quoted to the client for a single cleaning session.
    Formula: `Cost Base for Profit + Profit Amount Per Visit`
  8. Monthly/Frequency Bid: The total estimated price for all cleaning sessions within a month or billing cycle, based on the agreed frequency.
    Formula: `Estimated Bid Price Per Visit * Cleaning Frequency (visits per month)`

Variable Explanations:

The following variables are used in the cleaning bid calculator:

Variable Meaning Unit Typical Range
Total Area The total square footage of the property or area to be cleaned. sq ft 100 – 50,000+
Cleaning Frequency Number of cleaning visits scheduled within a typical month. Visits/Month 1 – 30
Target Hourly Rate The desired wage to pay each cleaner per hour worked. $/hr $15 – $40+
Estimated Cleaner Hours per Visit The total time (in hours) estimated for a cleaner to complete the job for one visit. Hours 0.5 – 10+
Supply Cost per Sq Ft The average cost of cleaning supplies allocated per square foot of space. $/sq ft $0.02 – $0.20
Overhead Percentage The percentage of total operating costs attributed to indirect business expenses. % 5% – 30%+
Desired Profit Margin The target profit you aim to achieve as a percentage of the total cost. % 10% – 35%+
Estimated Bid Price Per Visit The final price quoted for a single cleaning session. $ Varies greatly
Monthly/Frequency Bid The total price for all scheduled cleanings within a month. $ Varies greatly
Variables used in the Cleaning Bid Calculator and their typical values.

Practical Examples (Real-World Use Cases)

Let's illustrate how the cleaning bid calculator works with practical scenarios:

Example 1: Small Office Cleaning

A small business owner requires weekly cleaning for their 1,200 sq ft office space. They have identified their target hourly rate for cleaners and estimate the job will take 3 hours per visit.

Inputs:

  • Total Area: 1,200 sq ft
  • Cleaning Frequency: Weekly (4x per month)
  • Target Hourly Rate: $28/hr
  • Estimated Cleaner Hours per Visit: 3 hours
  • Supply Cost per Sq Ft: $0.07/sq ft
  • Overhead Percentage: 15%
  • Desired Profit Margin: 20%

Calculations:

  • Labor Cost Per Visit: 3 hrs * $28/hr = $84.00
  • Supply Cost Per Visit: 1,200 sq ft * $0.07/sq ft = $84.00
  • Total Operating Cost Per Visit: $84.00 + $84.00 = $168.00
  • Overhead Cost Per Visit: $168.00 * (15/100) = $25.20
  • Cost Base for Profit: $168.00 + $25.20 = $193.20
  • Profit Amount Per Visit: $193.20 * (20/100) = $38.64
  • Estimated Bid Price Per Visit: $193.20 + $38.64 = $231.84
  • Monthly/Frequency Bid: $231.84 * 4 = $927.36

Interpretation: For this weekly office cleaning, the cleaning bid calculator suggests a price of $231.84 per visit, totaling $927.36 per month. This price covers labor, supplies, overhead, and achieves the desired 20% profit margin.

Example 2: Deep Residential Cleaning (One-Time)

A homeowner requests a one-time deep clean for a 2,500 sq ft house. The cleaning service estimates it will take 8 hours of work for one cleaner.

Inputs:

  • Total Area: 2,500 sq ft
  • Cleaning Frequency: One-Time (1x per month for bid calculation)
  • Target Hourly Rate: $30/hr
  • Estimated Cleaner Hours per Visit: 8 hours
  • Supply Cost per Sq Ft: $0.10/sq ft
  • Overhead Percentage: 20%
  • Desired Profit Margin: 25%

Calculations:

  • Labor Cost Per Visit: 8 hrs * $30/hr = $240.00
  • Supply Cost Per Visit: 2,500 sq ft * $0.10/sq ft = $250.00
  • Total Operating Cost Per Visit: $240.00 + $250.00 = $490.00
  • Overhead Cost Per Visit: $490.00 * (20/100) = $98.00
  • Cost Base for Profit: $490.00 + $98.00 = $588.00
  • Profit Amount Per Visit: $588.00 * (25/100) = $147.00
  • Estimated Bid Price Per Visit: $588.00 + $147.00 = $735.00
  • Monthly/Frequency Bid: $735.00 * 1 = $735.00

Interpretation: For this intensive one-time deep clean, the cleaning bid calculator indicates a price of $735.00. This higher price reflects the extensive time commitment and higher supply usage typical of a deep clean, while ensuring adequate profit for the service provider. This is a great use case for a commercial cleaning cost estimator.

How to Use This Cleaning Bid Calculator

Using our free cleaning bid calculator is straightforward. Follow these steps to generate an accurate bid for your cleaning services:

  1. Input Property Size: Enter the Total Area in square feet (sq ft) of the space you need to clean.
  2. Specify Cleaning Frequency: Select how often the cleaning service will be performed using the Cleaning Frequency dropdown. Choose from one-time, weekly, bi-weekly, or other options. For one-time services, select "One-Time"; the calculator will base the "Monthly/Frequency Bid" on this single event.
  3. Set Labor Rate: Input your Target Hourly Rate per cleaner. This is the rate you aim to pay your staff, influencing the labor cost component.
  4. Estimate Time: Provide the Estimated Cleaner Hours per Visit. Be realistic about how long the cleaning will take.
  5. Factor in Supplies: Enter the Supply Cost per Sq Ft. This accounts for the cost of cleaning chemicals, tools, and consumables.
  6. Include Overhead: Input your Overhead Percentage. This covers indirect costs like insurance, vehicle expenses, and administrative tasks.
  7. Define Profit Goal: Set your Desired Profit Margin as a percentage. This is the profit you want to make on the job.
  8. Calculate: Click the "Calculate Bid" button. The calculator will instantly display your Estimated Bid Price Per Visit and the Monthly/Frequency Bid.

How to Read Results:

  • The Estimated Bid Price Per Visit is the recommended price for a single cleaning session.
  • The Monthly/Frequency Bid shows the total expected cost if the service is performed at the selected frequency over a month.
  • Intermediate values (Labor Cost, Supply Cost, Total Operating Cost) provide a breakdown of where the costs are coming from.
  • The chart and table offer a visual and detailed breakdown of the bid components.

Decision-Making Guidance:

  • Is the bid competitive? Research competitor pricing for similar services in your area.
  • Is the bid profitable? Ensure the profit margin meets your business goals. Adjust inputs if necessary.
  • Is the bid realistic? Double-check your time estimates and supply cost per square foot for accuracy.
  • Consider the client: For long-term contracts (like weekly services), you might offer a slight discount compared to one-off deep cleans.

Use the "Copy Results" button to easily share the bid details or save them for your records. The "Reset" button allows you to start fresh with default values.

Key Factors That Affect Cleaning Bid Results

Several factors can significantly influence the final price generated by a cleaning bid calculator. Understanding these elements helps in refining your inputs for more accurate and competitive bids.

  1. Scope of Work Complexity: A standard office clean differs vastly from a deep clean post-renovation or a biohazard cleanup. Tasks like heavy scrubbing, detailed dusting in hard-to-reach areas, window cleaning, or specialized floor care increase labor hours and potentially supply costs. Always define the scope clearly.
  2. Condition of the Property: A property that hasn't been cleaned in a long time will require significantly more effort and time than a regularly maintained space. This impacts Estimated Cleaner Hours per Visit.
  3. Accessibility and Layout: Buildings with multiple floors, complex layouts, limited access for equipment, or remote locations might incur additional time or transportation costs. This relates to effective commercial cleaning logistics.
  4. Client Expectations and Quality Standards: Some clients have extremely high standards requiring meticulous attention to detail, potentially extending cleaning time. Others may be satisfied with a more general tidiness. Discussing these expectations upfront is key.
  5. Staff Skill and Efficiency: Experienced and well-trained cleaning staff can often complete tasks more efficiently than new or less skilled workers. This affects the Estimated Cleaner Hours per Visit and can influence your Target Hourly Rate.
  6. Geographic Location and Market Rates: Labor costs (Target Hourly Rate), supply costs, and general overhead vary significantly by region. Wages in a major metropolitan area will be higher than in a rural town. Your Overhead Percentage might also be affected by local rent and insurance costs.
  7. Type and Quality of Cleaning Supplies/Equipment: Using eco-friendly, specialized, or premium cleaning solutions can increase Supply Cost per Sq Ft. Similarly, the cost and maintenance of advanced equipment (e.g., industrial floor polishers) factor into overhead.
  8. Urgency and Scheduling: Last-minute requests or cleaning services needed outside standard business hours (nights, weekends, holidays) may command a premium price due to inconvenience and potentially higher labor rates.

Frequently Asked Questions (FAQ)

What is the difference between Operating Cost and Total Cost?
Operating Cost includes direct expenses like labor and supplies for a specific job. Total Cost (or Cost Base for Profit) includes Operating Cost plus allocated Overhead expenses, giving a fuller picture of the resources consumed before profit is added.
How accurate are supply cost estimates?
Supply cost estimates are crucial. It's best to track your actual supply usage over time for different property types and sizes. Your Supply Cost per Sq Ft input should be based on this historical data for accuracy.
Can I use this calculator for both residential and commercial cleaning?
Yes, the principles of the cleaning bid calculator apply to both. You'll need to adjust the inputs (especially area, hours, and potentially supply costs) to match the specific requirements of residential versus commercial properties.
What if my actual cleaner hours are different from the estimate?
If actual hours consistently differ, you need to refine your estimation process. For recurring clients, you might adjust the bid after a few cleanings based on recorded times. Accurate time tracking is vital for improving bids over time. This relates to managing cleaning service pricing strategies.
How should I determine my Overhead Percentage?
Calculate your total annual indirect business expenses (rent, utilities, insurance, marketing, administrative salaries, software subscriptions, etc.) and divide by your total annual direct operating costs (labor + supplies for all jobs). Multiply by 100 to get the percentage.
Is a 20% profit margin good for a cleaning business?
A 20% profit margin is often considered healthy for many service-based businesses, including cleaning. However, optimal margins can vary based on market competition, business size, and specific service offerings. Some businesses aim for 15%, while others might target 25% or higher for specialized services.
What if a client negotiates the price down?
If a client negotiates, understand which part of the bid they are questioning. If they push on price, you can refer back to the value provided (quality, reliability, comprehensive service). You might consider slightly reducing your profit margin or finding efficiencies, but avoid cutting into essential costs like labor or supplies to maintain profitability and service quality.
How often should I update my cleaning bid calculations?
It's advisable to review and update your bid calculations at least annually, or whenever significant changes occur. Factors like rising supply costs, changes in labor wages, new insurance premiums, or shifts in market rates necessitate recalculating your bids to remain accurate and profitable.

© 2023 Your Company Name. All rights reserved.

var sqFtInput = document.getElementById('squareFootage'); var freqSelect = document.getElementById('cleaningFrequency'); var hrRateInput = document.getElementById('hourlyRate'); var cleanerHrsInput = document.getElementById('cleanerHours'); var supplyCostInput = document.getElementById('supplyCostPerSqFt'); var overheadInput = document.getElementById('overheadPercentage'); var profitInput = document.getElementById('profitMarginPercentage'); var resultsWrapper = document.getElementById('resultsWrapper'); var estimatedBidPerVisitSpan = document.getElementById('estimatedBidPerVisit'); var laborCostPerVisitSpan = document.getElementById('laborCostPerVisit'); var supplyCostPerVisitSpan = document.getElementById('supplyCostPerVisit'); var totalOperatingCostPerVisitSpan = document.getElementById('totalOperatingCostPerVisit'); var monthlyBidSpan = document.getElementById('monthlyBid'); var chartContainer = document.getElementById('chartContainer'); var bidChartCanvas = document.getElementById('bidChart'); var bidTableBody = document.getElementById('bidTableBody'); var tableContainer = document.getElementById('tableContainer'); var ctx; var bidChartInstance = null; function formatCurrency(amount) { return "$" + amount.toFixed(2); } function validateInput(inputId, errorId, minValue, maxValue) { var input = document.getElementById(inputId); var errorElement = document.getElementById(errorId); var value = parseFloat(input.value); if (isNaN(value) || input.value.trim() === "") { errorElement.textContent = "This field is required."; return false; } if (value < 0) { errorElement.textContent = "Cannot be negative."; return false; } if (minValue !== undefined && value maxValue) { errorElement.textContent = "Value cannot exceed " + maxValue + "."; return false; } errorElement.textContent = ""; return true; } function calculateBid() { // Clear previous errors document.querySelectorAll('.error-message').forEach(function(el) { el.textContent = "; }); // Validate inputs var isValid = true; isValid &= validateInput('squareFootage', 'squareFootageError', 0); isValid &= validateInput('hourlyRate', 'hourlyRateError', 0); isValid &= validateInput('cleanerHours', 'cleanerHoursError', 0); isValid &= validateInput('supplyCostPerSqFt', 'supplyCostPerSqFtError', 0); isValid &= validateInput('overheadPercentage', 'overheadPercentageError', 0, 100); isValid &= validateInput('profitMarginPercentage', 'profitMarginPercentageError', 0, 100); if (!isValid) { resultsWrapper.style.display = 'none'; chartContainer.style.display = 'none'; tableContainer.style.display = 'none'; return; } var squareFootage = parseFloat(sqFtInput.value); var cleaningFrequency = parseInt(freqSelect.value); var hourlyRate = parseFloat(hrRateInput.value); var cleanerHours = parseFloat(cleanerHrsInput.value); var supplyCostPerSqFt = parseFloat(supplyCostInput.value); var overheadPercentage = parseFloat(overheadInput.value); var profitMarginPercentage = parseFloat(profitInput.value); // Calculations var laborCostPerVisit = cleanerHours * hourlyRate; var supplyCostPerVisit = squareFootage * supplyCostPerSqFt; var totalOperatingCostPerVisit = laborCostPerVisit + supplyCostPerVisit; var overheadCostPerVisit = totalOperatingCostPerVisit * (overheadPercentage / 100); var costBaseForProfit = totalOperatingCostPerVisit + overheadCostPerVisit; var profitAmountPerVisit = costBaseForProfit * (profitMarginPercentage / 100); var estimatedBidPerVisit = costBaseForProfit + profitAmountPerVisit; var monthlyBid = estimatedBidPerVisit * cleaningFrequency; // Update Results Display estimatedBidPerVisitSpan.textContent = formatCurrency(estimatedBidPerVisit); laborCostPerVisitSpan.textContent = formatCurrency(laborCostPerVisit); supplyCostPerVisitSpan.textContent = formatCurrency(supplyCostPerVisit); totalOperatingCostPerVisitSpan.textContent = formatCurrency(totalOperatingCostPerVisit); monthlyBidSpan.textContent = formatCurrency(monthlyBid); resultsWrapper.style.display = 'block'; // Update Chart and Table updateChartAndTable(estimatedBidPerVisit, laborCostPerVisit, supplyCostPerVisit, overheadCostPerVisit, profitAmountPerVisit); chartContainer.style.display = 'block'; tableContainer.style.display = 'block'; } function updateChartAndTable(bid, labor, supplies, overhead, profit) { // Clear previous chart if it exists if (bidChartInstance) { bidChartInstance.destroy(); } // Update Table bidTableBody.innerHTML = ` Labor Cost ${formatCurrency(labor)} ${(labor / bid * 100).toFixed(1)}% Supply Cost ${formatCurrency(supplies)} ${(supplies / bid * 100).toFixed(1)}% Overhead Cost ${formatCurrency(overhead)} ${(overhead / bid * 100).toFixed(1)}% Profit ${formatCurrency(profit)} ${(profit / bid * 100).toFixed(1)}% `; // Update Chart ctx = bidChartCanvas.getContext('2d'); bidChartInstance = new Chart(ctx, { type: 'pie', data: { labels: ['Labor Cost', 'Supply Cost', 'Overhead Cost', 'Profit'], datasets: [{ data: [labor, supplies, overhead, profit], backgroundColor: [ '#004a99', // Primary Blue '#6c757d', // Secondary Gray '#ffc107', // Warning Yellow '#28a745' // Success Green ], borderColor: '#ffffff', borderWidth: 1 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', }, tooltip: { callbacks: { label: function(tooltipItem) { var label = tooltipItem.label || "; if (label) { label += ': '; } label += formatCurrency(tooltipItem.raw); return label; } } } } } }); } function resetCalculator() { sqFtInput.value = ""; freqSelect.value = "4"; // Default to Weekly hrRateInput.value = "25"; cleanerHrsInput.value = ""; supplyCostInput.value = "0.05"; overheadInput.value = "15"; profitInput.value = "20"; // Clear errors document.querySelectorAll('.error-message').forEach(function(el) { el.textContent = "; }); // Hide results and chart/table resultsWrapper.style.display = 'none'; chartContainer.style.display = 'none'; tableContainer.style.display = 'none'; // Destroy chart instance if exists if (bidChartInstance) { bidChartInstance.destroy(); bidChartInstance = null; } } function copyResults() { var mainResult = estimatedBidPerVisitSpan.textContent; var laborCost = laborCostPerVisitSpan.textContent; var supplyCost = supplyCostPerVisitSpan.textContent; var operatingCost = totalOperatingCostPerVisitSpan.textContent; var monthlyTotal = monthlyBidSpan.textContent; var assumptions = ` Key Assumptions: – Square Footage: ${sqFtInput.value} sq ft – Frequency: ${freqSelect.options[freqSelect.selectedIndex].text} – Hourly Rate: ${hrRateInput.value}/hr – Cleaner Hours per Visit: ${cleanerHrsInput.value} hrs – Supply Cost per Sq Ft: ${supplyCostInput.value}/sq ft – Overhead: ${overheadInput.value}% – Profit Margin: ${profitInput.value}% `; var copyText = `— Cleaning Bid Summary — Bid Price Per Visit: ${mainResult} Monthly/Frequency Bid: ${monthlyTotal} — Cost Breakdown — Labor Cost Per Visit: ${laborCost} Supply Cost Per Visit: ${supplyCost} Total Operating Cost Per Visit: ${operatingCost} ${assumptions} `; navigator.clipboard.writeText(copyText).then(function() { var originalText = document.querySelector('.copy-btn').textContent; document.querySelector('.copy-btn').textContent = 'Copied!'; setTimeout(function() { document.querySelector('.copy-btn').textContent = originalText; }, 2000); }).catch(function(err) { console.error('Failed to copy text: ', err); var originalText = document.querySelector('.copy-btn').textContent; document.querySelector('.copy-btn').textContent = 'Copy Failed'; setTimeout(function() { document.querySelector('.copy-btn').textContent = originalText; }, 2000); }); } // — Chart.js library included directly — // Minimal Chart.js implementation for pie chart var Chart = (function() { function Chart(context, config) { this.context = context; this.config = config; this.canvas = context.canvas; this.width = this.canvas.width; this.height = this.canvas.height; this.render(); } Chart.prototype.render = function() { var ctx = this.context; var data = this.config.data; var options = this.config.options || {}; var type = this.config.type; if (type === 'pie') { this.renderPieChart(data, options); } }; Chart.prototype.renderPieChart = function(data, options) { var ctx = this.context; var width = this.width; var height = this.height; var centerX = width / 2; var centerY = height / 2; var radius = Math.min(width, height) / 2; var total = data.datasets[0].data.reduce(function(acc, val) { return acc + val; }, 0); var colors = data.datasets[0].backgroundColor; var labels = data.labels; var startAngle = 0; // Draw slices for (var i = 0; i < data.datasets[0].data.length; i++) { var sliceAngle = (data.datasets[0].data[i] / total) * 2 * Math.PI; ctx.beginPath(); ctx.moveTo(centerX, centerY); ctx.arc(centerX, centerY, radius, startAngle, startAngle + sliceAngle); ctx.closePath(); ctx.fillStyle = colors[i % colors.length]; ctx.fill(); startAngle += sliceAngle; } // Draw legend if (options.plugins && options.plugins.legend && options.plugins.legend.position === 'bottom') { this.drawLegend(data.labels, colors, options.plugins.legend.position); } }; Chart.prototype.drawLegend = function(labels, colors, position) { var legendContainer = document.createElement('div'); legendContainer.style.textAlign = 'center'; legendContainer.style.marginTop = '15px'; // Space below the chart canvas for (var i = 0; i < labels.length; i++) { var legendItem = document.createElement('span'); legendItem.style.display = 'inline-block'; legendItem.style.margin = '0 10px'; legendItem.style.fontSize = '0.85em'; legendItem.style.color = 'var(–secondary-text-color)'; var colorBox = document.createElement('span'); colorBox.style.display = 'inline-block'; colorBox.style.width = '12px'; colorBox.style.height = '12px'; colorBox.style.backgroundColor = colors[i % colors.length]; colorBox.style.marginRight = '5px'; colorBox.style.verticalAlign = 'middle'; colorBox.style.border = '1px solid var(–border-color)'; legendItem.appendChild(colorBox); legendItem.appendChild(document.createTextNode(labels[i])); legendContainer.appendChild(legendItem); } // Append legend below the canvas, inside the chart-container if (this.config.options.plugins.legend.position === 'bottom') { var chartContainer = this.canvas.parentElement; chartContainer.appendChild(legendContainer); } }; Chart.prototype.destroy = function() { // Basic cleanup for this simplified implementation this.context.canvas.width = this.context.canvas.width; // Clear canvas // Remove dynamically added legend if necessary (though it's appended to parent) var legend = this.canvas.parentElement.querySelector('div[style*="display: inline-block"]'); if (legend) { legend.remove(); } }; return Chart; })(); // Initial setup for FAQ toggles document.addEventListener('DOMContentLoaded', function() { var faqItems = document.querySelectorAll('.faq-item'); faqItems.forEach(function(item) { var question = item.querySelector('.question'); question.addEventListener('click', function() { item.classList.toggle('open'); }); }); }); // Trigger initial calculation if default values are present and valid if (sqFtInput.value && hrRateInput.value && cleanerHrsInput.value && supplyCostInput.value && overheadInput.value && profitInput.value) { // Check if any input has a meaningful default value before calculating var hasDefaultValue = parseFloat(hrRateInput.value) !== 0 || parseFloat(supplyCostInput.value) !== 0 || parseFloat(overheadInput.value) !== 0 || parseFloat(profitInput.value) !== 0; if(hasDefaultValue) { calculateBid(); } }

Leave a Comment