Pricing Calculator

import React, { useMemo, useState } from "react"; // ---- Pricing data ---- const MIN_CHARGE = 475; // minimum subtotal before tax const DEFAULT_MARKUP = 0.35; // 35% const FORCED_300_MARKUP_SERVICES = ["glass_replacement", "mirror", "repair"]; const CATALOG = { sqft: { glass_replacement: { "Clear 3/16\"": 2.50 * 1.3, "Clear 1/4\"": 2.50 * 1.3, "Clear 3/8\"": 6.46 * 1.3, "Clear 1/2\"": 9.64 * 1.3, "Clear 3/4\"": 26.59 * 1.3, "Gray 1/4\"": 7.58 * 1.3, "Bronze/Gray 1/4\"": 5.39 * 1.3, "Bronze/Gray 3/8\"": 14.46 * 1.3, "Starphire 1/4\"": 6.25 * 1.3, "Starphire 3/8\"": 10.51 * 1.3, "Starphire 1/2\"": 20.42 * 1.3, "Starphire 3/4\"": 29.09 * 1.3, "Acid Reg 1/4\"": 10.46 * 1.3, "Acid Low-Iron 1/4\"": 15.26 * 1.3 }, mirror: { "Mirror Clear 1/4\"": 4.00 * 1.3, "Mirror Bronze/Gray 1/4\"": 6.50 * 1.3, "One Way Mirror 1/4\"": 14.16 * 1.3, "Low-E 1/4\"": 5.88 * 1.3 }, insulated_glass_unit: { "IGU 7/8\" Clear Annealed": 18.5, "IGU 7/8\" Clear Tempered": 20.0, "IGU 7/8\" Low-E Annealed": 19.75, "IGU 7/8\" Low-E Tempered": 21.25, "IGU 1\" Clear Annealed": 19.0, "IGU 1\" Clear Tempered": 20.5, "IGU 1\" Low-E Annealed": 20.25, "IGU 1\" Low-E Tempered": 21.75 }, screen_fabrication: { "Fiberglass Screen": 7.07 }, repair: { "General Repair": 0 } }, edge: { polish_in: 0.07 * 1.3, polish_in_thick: 0.50 * 1.3, miter_in: 0.13 * 1.3, hole_each: 11.13 * 1.3, notch_each: 16.69 * 1.3, patch_each: 60.26 * 1.3, }, parts: [ { sku: "IGU-SPACER-ALUM", name: "Aluminum Spacer (ea)", cost: 5.00 }, { sku: "IGU-GRIDS", name: "Grids (ea)", cost: 12.00 }, { sku: "BAL-RED", name: "Red Tip Balance (ea)", cost: 5.75 }, { sku: "BAL-BLACK-31-39", name: "Black Tip Balance 31–39\" (ea)", cost: 7.25 }, { sku: "BAL-BLACK-10-30", name: "Black Tip Balance 10–30\" (ea)", cost: 7.00 }, { sku: "A2.5-SHOE", name: "A2.5 Shoe (ea)", cost: 1.35 }, { sku: "PIVOT-TILT-BAR", name: "Pivot Tilt Bar (ea)", cost: 0.65 }, { sku: "TOP-MOUNT-LATCH", name: "Top Mount Latch (ea)", cost: 1.25 }, { sku: "SCREEN-FB-ROLL-42x100", name: "Fiberglass Mesh Roll 42\"×100'", cost: 63.00 }, ], }; const RATES = { glazier: 95, helper: 55 }; const fmt = (n)=> n.toLocaleString(undefined,{style:"currency",currency:"USD"}); const toSqft = (w,h)=> Math.max(0,(w*h)/144); const perimeter = (w,h)=> Math.max(0,2*(w+h)); export default function App(){ // Basic inputs const [service, setService] = useState("glass_replacement"); const variantList = Object.keys(CATALOG.sqft[service] || {}); const [variant, setVariant] = useState(variantList[0] || ""); const [qty, setQty] = useState(1); const [w, setW] = useState(36); const [h, setH] = useState(60); const [tempered, setTempered] = useState(false); const [laminated, setLaminated] = useState(false); const [lowe, setLowe] = useState(true); const [tinted, setTinted] = useState(false); const [soundproof, setSoundproof] = useState(false); // Edgework const [polish, setPolish] = useState(false); const [polishThick, setPolishThick] = useState(false); const [miter, setMiter] = useState(false); const [holes, setHoles] = useState(0); const [notches, setNotches] = useState(0); const [patches, setPatches] = useState(0); // Parts const [parts, setParts] = useState([]); // {sku, name, qty, cost} // Labor/fees/markup const [hoursG, setHoursG] = useState(3); const [hoursH, setHoursH] = useState(2); const [travel, setTravel] = useState(95); const [disposal, setDisposal] = useState(35); const forced300 = FORCED_300_MARKUP_SERVICES.includes(service); const [markup, setMarkup] = useState( forced300 ? 3.0 : DEFAULT_MARKUP ); const [taxRate, setTaxRate] = useState(0.08875); // Sync variant when service changes React.useEffect(()=>{ setMarkup(forced300 ? 3.0 : DEFAULT_MARKUP); const first = Object.keys(CATALOG.sqft[service] || {})[0] || ""; setVariant(first); },[service]); // Material cost const sqft = useMemo(()=> toSqft(w,h),[w,h]); const rate = CATALOG.sqft[service]?.[variant] ?? 0; let material = (sqft * 1.05) * rate * qty; // 5% waste if(["glass_replacement","mirror"].includes(service)){ if(tempered) material *= 1.15; if(laminated) material *= 1.25; if(lowe) material *= 1.08; if(tinted) material *= 1.05; if(soundproof) material *= 1.12; } // Edgework const perim = useMemo(()=> perimeter(w,h),[w,h]); const edge = useMemo(()=>{ let c=0; if(polish){ c += perim * (polishThick ? CATALOG.edge.polish_in_thick : CATALOG.edge.polish_in) * qty; } if(miter){ c += perim * CATALOG.edge.miter_in * qty; } c += holes * CATALOG.edge.hole_each; c += notches * CATALOG.edge.notch_each; c += patches * CATALOG.edge.patch_each; return c; },[polish,polishThick,miter,holes,notches,patches,perim,qty]); // Parts const partsCost = useMemo(()=> parts.reduce((s,p)=> s + (Number(p.cost)||0)*(Number(p.qty)||0),0),[parts]); // Labor const labor = (hoursG*RATES.glazier + hoursH*RATES.helper); // Subtotal with min charge const subtotalRaw = material + edge + partsCost + labor + travel + disposal; const subtotal = Math.max(subtotalRaw, MIN_CHARGE); // Pricing const beforeTax = subtotal * (1 + markup); const tax = beforeTax * taxRate; const total = beforeTax + tax; const margin = beforeTax ? (beforeTax - subtotal)/beforeTax : 0; const addPart = (sku)=>{ const p = CATALOG.parts.find(x=>x.sku===sku); if(!p) return; setParts(prev=>[...prev,{...p, qty:1}]); }; return (
{/* LEFT: inputs */}

Mr. Glazier — Pricing Calculator

Focus: Glass • Mirrors • IGU • Screens • Repair
{/* Service & variant */}
setQty(Number(e.target.value)||0)} />
{/* Size */}
setW(Number(e.target.value)||0)} />
setH(Number(e.target.value)||0)} />
Area: {sqft.toFixed(2)} sqft • Perimeter: {perim.toFixed(1)} in
{/* Glass/Mirror options */} {["glass_replacement","mirror"].includes(service) && (
{[{k:"tempered",v:tempered,s:setTempered},{k:"laminated",v:laminated,s:setLaminated},{k:"lowe",v:lowe,s:setLowe},{k:"tinted",v:tinted,s:setTinted},{k:"soundproof",v:soundproof,s:setSoundproof}].map(o=> ( ))}
)} {/* Edgework */}
Edgework & Fabrication
setHoles(Number(e.target.value)||0)} />
setNotches(Number(e.target.value)||0)} />
setPatches(Number(e.target.value)||0)} />
{/* Parts */}
Parts & Hardware
Edit qty or unit cost as needed:
{parts.length===0 &&
No parts yet.
} {parts.map((p,i)=> (
setParts(prev=> prev.map((r,idx)=> idx===i?{...r,sku:e.target.value}:r))} /> setParts(prev=> prev.map((r,idx)=> idx===i?{...r,name:e.target.value}:r))} /> setParts(prev=> prev.map((r,idx)=> idx===i?{...r,qty:Number(e.target.value)||0}:r))} /> setParts(prev=> prev.map((r,idx)=> idx===i?{...r,cost:Number(e.target.value)||0}:r))} />
))}
{/* Labor, fees, markup */}
setHoursG(Number(e.target.value)||0)} />
{fmt(RATES.glazier)}/hr
setHoursH(Number(e.target.value)||0)} />
{fmt(RATES.helper)}/hr
setTravel(Number(e.target.value)||0)} />
setDisposal(Number(e.target.value)||0)} />
setMarkup((Number(e.target.value)||0)/100)} />
{/* RIGHT: totals */}
Totals
Material
{fmt(material)}
Edgework
{fmt(edge)}
Parts
{fmt(partsCost)}
Labor
{fmt(labor)}
Travel
{fmt(travel)}
Disposal
{fmt(disposal)}
Subtotal (min {fmt(MIN_CHARGE)})
{fmt(subtotal)}
Markup
{((markup)*100).toFixed(1)}%
Before Tax
{fmt(beforeTax)}
Sales Tax
setTaxRate((Number(e.target.value)||0)/100)} /> %
Total
{fmt(total)}
Margin: {(margin*100).toFixed(1)}%
); }