@metron-labs/quote-react 0.1.61 → 0.1.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,34 +1,21 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
- import BrickWall from "../BrickWall";
5
- import { useOptionalCart } from "../../lib/cart/CartContext";
6
- import { candidateToCartItem } from "../../lib/boq/toCartItem";
7
- import { searchCatalogue } from "../../lib/estimators/resolveProducts";
8
4
  const SITE_MIX_DRY_VOLUME_FACTOR = 1.54;
9
5
  const CEMENT_DENSITY_KG_M3 = 1440;
10
- const CEMENT_BAG_KG = 25;
11
- const BULK_BAG_KG = 800;
12
- const MIN_BASKET_LOADING_MS = 1500;
13
6
  const toNumber = (value) => Number.parseFloat(value) || 0;
14
7
  const aggregateDetails = {
15
- mot: { label: "MOT Type 1", blurb: "Sub-base for patios and driveways", density: 1.7, productId: "6001007" },
16
- "sharp-sand": { label: "Sharp sand", blurb: "Bedding and general groundwork", density: 1.6, productId: "6001012" },
17
- gravel: { label: "20mm gravel", blurb: "Drainage and landscaping", density: 1.6, productId: "6001004" },
8
+ mot: { label: "MOT Type 1", blurb: "Sub-base for patios and driveways", density: 1.7, query: "MOT type 1" },
9
+ "sharp-sand": { label: "Sharp sand", blurb: "Bedding and general groundwork", density: 1.6, query: "sharp sand" },
10
+ gravel: { label: "20mm gravel", blurb: "Drainage and landscaping", density: 1.6, query: "20mm gravel" },
18
11
  };
19
12
  export default function ConcreteAggregateEstimator({ onBack, onReview, submitLabel = "Add to basket" }) {
20
- // This calculator knows its exact products, so it normally fills the basket
21
- // itself. The internal tools render it outside the cart provider and take the
22
- // quantities through their own review instead, so the cart is optional here.
23
- const cart = useOptionalCart();
24
13
  const [unit, setUnit] = useState("m");
25
14
  const [length, setLength] = useState("");
26
15
  const [width, setWidth] = useState("");
27
16
  const [depth, setDepth] = useState("");
28
17
  const [group, setGroup] = useState("concrete");
29
18
  const [aggregate, setAggregate] = useState("mot");
30
- const [adding, setAdding] = useState(false);
31
- const [basketError, setBasketError] = useState(null);
32
19
  const conversion = unit === "m" ? 1 : 0.3048;
33
20
  const volumeM3 = toNumber(length) * conversion * toNumber(width) * conversion * toNumber(depth) * (unit === "m" ? 0.01 : 0.0254);
34
21
  const selectedAggregate = aggregateDetails[aggregate];
@@ -36,66 +23,17 @@ export default function ConcreteAggregateEstimator({ onBack, onReview, submitLab
36
23
  const cementKg = siteMixPartVolume * CEMENT_DENSITY_KG_M3;
37
24
  const sandVolumeM3 = siteMixPartVolume * 2;
38
25
  const concreteAggregateVolumeM3 = siteMixPartVolume * 4;
39
- const cementBags = Math.ceil(cementKg / CEMENT_BAG_KG);
40
- const sandBulkBags = Math.ceil((sandVolumeM3 * aggregateDetails["sharp-sand"].density * 1000) / BULK_BAG_KG);
41
- const concreteAggregateBulkBags = Math.ceil((concreteAggregateVolumeM3 * aggregateDetails.gravel.density * 1000) / BULK_BAG_KG);
42
- const aggregateBulkBags = Math.ceil((volumeM3 * selectedAggregate.density * 1000) / BULK_BAG_KG);
26
+ const kg = (volume, density) => Math.ceil(volume * density * 1000);
27
+ // Asked for by weight: the basket turns it into whatever bags the merchant sells, through the
28
+ // same product table and pricing as the other calculators.
43
29
  const requirements = group === "concrete"
44
30
  ? [
45
- { key: "cement", label: "Blue Circle General Purpose Grey Cement 25kg", qty: cementBags, productId: "6003004" },
46
- { key: "sharp-sand", label: "Sharp Sand Bulk Bag (800kg)", qty: sandBulkBags, productId: "6001012" },
47
- { key: "gravel", label: "20mm Non Decorative Pea Shingle/Gravel Bulk Bag (800kg)", qty: concreteAggregateBulkBags, productId: "6001004" },
31
+ { key: "cement", label: "General purpose cement", qty: Math.ceil(cementKg), unit: "kg", query: "general purpose cement" },
32
+ { key: "sharp-sand", label: "Sharp sand", qty: kg(sandVolumeM3, aggregateDetails["sharp-sand"].density), unit: "kg", query: aggregateDetails["sharp-sand"].query },
33
+ { key: "gravel", label: "20mm gravel", qty: kg(concreteAggregateVolumeM3, aggregateDetails.gravel.density), unit: "kg", query: aggregateDetails.gravel.query },
48
34
  ]
49
- : [{ key: aggregate, label: `${selectedAggregate.label} Bulk Bag (800kg)`, qty: aggregateBulkBags, productId: selectedAggregate.productId }];
50
- // Handed up as requirements when the host has its own review step; the
51
- // product code is the query because that is what resolves these exact lines.
52
- const submit = () => {
53
- if (onReview) {
54
- onReview(requirements.map((requirement) => ({
55
- key: requirement.key,
56
- label: requirement.label,
57
- qty: requirement.qty,
58
- unit: "each",
59
- query: requirement.productId,
60
- })));
61
- return;
62
- }
63
- void addToBasket();
64
- };
65
- const addToBasket = async () => {
66
- const startedAt = Date.now();
67
- setAdding(true);
68
- setBasketError(null);
69
- try {
70
- const items = await Promise.all(requirements.map(async (requirement) => {
71
- const candidate = (await searchCatalogue(requirement.productId)).find((item) => item.product_id === requirement.productId);
72
- if (!candidate)
73
- throw new Error(`${requirement.label} is no longer available in this catalogue.`);
74
- return candidateToCartItem(candidate, {
75
- lineId: `concrete-aggregate:${requirement.key}`,
76
- description: requirement.label,
77
- unit: "bag",
78
- qty: requirement.qty,
79
- });
80
- }));
81
- const remaining = MIN_BASKET_LOADING_MS - (Date.now() - startedAt);
82
- if (remaining > 0)
83
- await new Promise((resolve) => window.setTimeout(resolve, remaining));
84
- cart?.clearCart();
85
- cart?.addManyToCart(items);
86
- cart?.openBasket();
87
- }
88
- catch (error) {
89
- setBasketError(error instanceof Error ? error.message : "Could not add the products to your basket.");
90
- }
91
- finally {
92
- setAdding(false);
93
- }
94
- };
95
- if (adding) {
96
- return (_jsx(_Fragment, { children: _jsxs("div", { className: "truss-estimator", children: [_jsxs("div", { className: "quick-order-hero", children: [_jsx("div", { className: "quick-order-eyebrow", children: "Estimating Tools" }), _jsx("h1", { children: "Your Materials" }), _jsx("p", { children: "Calculating quantities from the Concrete and Aggregate Calculator." })] }), _jsx("div", { className: "truss-estimator__card", children: _jsxs("div", { className: "processing-container processing-variant-banter", role: "status", "aria-label": "Loading", style: { flex: 1, justifyContent: "center" }, children: [_jsx(BrickWall, {}), _jsx("p", { className: "banter-message is-visible", children: "Preparing your materials\u2026" })] }) })] }) }));
97
- }
98
- return (_jsx(_Fragment, { children: _jsxs("div", { className: "truss-estimator", children: [_jsxs("div", { className: "quick-order-hero", children: [_jsx("div", { className: "quick-order-eyebrow", children: "Estimating Tools" }), _jsx("h1", { children: "Concrete and Aggregate Calculator" }), _jsx("p", { children: "Calculate fixed site-mix concrete ingredients or aggregate bulk bags." })] }), _jsxs("div", { className: "truss-estimator__card materials-calculator", children: [_jsx("h2", { className: "truss-estimator__step-title", children: "Choose what you need" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Enter the area and depth. The calculator adds the fixed Materials Market products directly to your basket." }), _jsxs("div", { className: "materials-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Material type" }), _jsxs("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: [_jsxs("button", { type: "button", className: `truss-estimator__option-card${group === "concrete" ? " is-selected" : ""}`, onClick: () => setGroup("concrete"), children: [_jsx("div", { className: "truss-estimator__option-title", children: "Site-mixed concrete" }), _jsx("div", { className: "truss-estimator__option-copy", children: "Cement, sharp sand and 20mm aggregate" })] }), _jsxs("button", { type: "button", className: `truss-estimator__option-card${group === "aggregate" ? " is-selected" : ""}`, onClick: () => setGroup("aggregate"), children: [_jsx("div", { className: "truss-estimator__option-title", children: "Aggregates" }), _jsx("div", { className: "truss-estimator__option-copy", children: "MOT Type 1, sharp sand or 20mm gravel" })] })] })] }), group === "aggregate" && (_jsxs("div", { className: "materials-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Aggregate type" }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: Object.keys(aggregateDetails).map((id) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${aggregate === id ? " is-selected" : ""}`, onClick: () => setAggregate(id), children: [_jsx("div", { className: "truss-estimator__option-title", children: aggregateDetails[id].label }), _jsx("div", { className: "truss-estimator__option-copy", children: aggregateDetails[id].blurb })] }, id))) })] })), _jsxs("div", { className: "materials-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Area and depth" }), _jsxs("div", { className: "materials-calculator__unit-row", children: [_jsx("span", { children: "Units" }), _jsxs("div", { className: "brick-calculator__unit-toggle", role: "group", "aria-label": "Measurement units", children: [_jsx("button", { type: "button", className: unit === "m" ? "is-selected" : "", onClick: () => setUnit("m"), children: "Metric" }), _jsx("button", { type: "button", className: unit === "ft" ? "is-selected" : "", onClick: () => setUnit("ft"), children: "Imperial" })] })] }), _jsxs("div", { className: "truss-estimator__field-grid", children: [_jsxs("label", { className: "truss-estimator__field", children: [_jsxs("span", { children: ["Length (", unit, ")"] }), _jsx("input", { type: "number", min: "0", step: "0.01", value: length, onChange: (event) => setLength(event.target.value) })] }), _jsxs("label", { className: "truss-estimator__field", children: [_jsxs("span", { children: ["Width (", unit, ")"] }), _jsx("input", { type: "number", min: "0", step: "0.01", value: width, onChange: (event) => setWidth(event.target.value) })] })] }), _jsxs("label", { className: "truss-estimator__field materials-calculator__depth", children: [_jsxs("span", { children: ["Depth (", unit === "m" ? "cm" : "inches", ")"] }), _jsx("input", { type: "number", min: "0", step: "0.1", value: depth, onChange: (event) => setDepth(event.target.value) })] })] }), _jsxs("div", { className: "materials-calculator__totals", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Materials required" }), _jsx("div", { className: "materials-calculator__result-grid", children: group === "concrete" ? (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("span", { children: "Cement" }), _jsxs("strong", { children: [cementKg.toFixed(0), " kg"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Sharp sand" }), _jsxs("strong", { children: [sandVolumeM3.toFixed(2), " m\u00B3"] })] }), _jsxs("div", { children: [_jsx("span", { children: "20mm aggregate" }), _jsxs("strong", { children: [concreteAggregateVolumeM3.toFixed(2), " m\u00B3"] })] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("span", { children: selectedAggregate.label }), _jsxs("strong", { children: [volumeM3.toFixed(2), " m\u00B3"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Approx. weight" }), _jsxs("strong", { children: [(volumeM3 * selectedAggregate.density).toFixed(2), " t"] })] }), _jsx("div", {})] })) }), _jsx("p", { className: "materials-calculator__note", children: group === "concrete"
35
+ : [{ key: aggregate, label: selectedAggregate.label, qty: kg(volumeM3, selectedAggregate.density), unit: "kg", query: selectedAggregate.query }];
36
+ return (_jsx(_Fragment, { children: _jsxs("div", { className: "truss-estimator", children: [_jsxs("div", { className: "quick-order-hero", children: [_jsx("div", { className: "quick-order-eyebrow", children: "Estimating Tools" }), _jsx("h1", { children: "Concrete and Aggregate Calculator" }), _jsx("p", { children: "Calculate fixed site-mix concrete ingredients or aggregate bulk bags." })] }), _jsxs("div", { className: "truss-estimator__card materials-calculator", children: [_jsx("h2", { className: "truss-estimator__step-title", children: "Choose what you need" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Enter the area and depth. The calculator works out the materials and adds them to your basket." }), _jsxs("div", { className: "materials-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Material type" }), _jsxs("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: [_jsxs("button", { type: "button", className: `truss-estimator__option-card${group === "concrete" ? " is-selected" : ""}`, onClick: () => setGroup("concrete"), children: [_jsx("div", { className: "truss-estimator__option-title", children: "Site-mixed concrete" }), _jsx("div", { className: "truss-estimator__option-copy", children: "Cement, sharp sand and 20mm aggregate" })] }), _jsxs("button", { type: "button", className: `truss-estimator__option-card${group === "aggregate" ? " is-selected" : ""}`, onClick: () => setGroup("aggregate"), children: [_jsx("div", { className: "truss-estimator__option-title", children: "Aggregates" }), _jsx("div", { className: "truss-estimator__option-copy", children: "MOT Type 1, sharp sand or 20mm gravel" })] })] })] }), group === "aggregate" && (_jsxs("div", { className: "materials-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Aggregate type" }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: Object.keys(aggregateDetails).map((id) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${aggregate === id ? " is-selected" : ""}`, onClick: () => setAggregate(id), children: [_jsx("div", { className: "truss-estimator__option-title", children: aggregateDetails[id].label }), _jsx("div", { className: "truss-estimator__option-copy", children: aggregateDetails[id].blurb })] }, id))) })] })), _jsxs("div", { className: "materials-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Area and depth" }), _jsxs("div", { className: "materials-calculator__unit-row", children: [_jsx("span", { children: "Units" }), _jsxs("div", { className: "brick-calculator__unit-toggle", role: "group", "aria-label": "Measurement units", children: [_jsx("button", { type: "button", className: unit === "m" ? "is-selected" : "", onClick: () => setUnit("m"), children: "Metric" }), _jsx("button", { type: "button", className: unit === "ft" ? "is-selected" : "", onClick: () => setUnit("ft"), children: "Imperial" })] })] }), _jsxs("div", { className: "truss-estimator__field-grid", children: [_jsxs("label", { className: "truss-estimator__field", children: [_jsxs("span", { children: ["Length (", unit, ")"] }), _jsx("input", { type: "number", min: "0", step: "0.01", value: length, onChange: (event) => setLength(event.target.value) })] }), _jsxs("label", { className: "truss-estimator__field", children: [_jsxs("span", { children: ["Width (", unit, ")"] }), _jsx("input", { type: "number", min: "0", step: "0.01", value: width, onChange: (event) => setWidth(event.target.value) })] })] }), _jsxs("label", { className: "truss-estimator__field materials-calculator__depth", children: [_jsxs("span", { children: ["Depth (", unit === "m" ? "cm" : "inches", ")"] }), _jsx("input", { type: "number", min: "0", step: "0.1", value: depth, onChange: (event) => setDepth(event.target.value) })] })] }), _jsxs("div", { className: "materials-calculator__totals", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Materials required" }), _jsx("div", { className: "materials-calculator__result-grid", children: group === "concrete" ? (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("span", { children: "Cement" }), _jsxs("strong", { children: [cementKg.toFixed(0), " kg"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Sharp sand" }), _jsxs("strong", { children: [sandVolumeM3.toFixed(2), " m\u00B3"] })] }), _jsxs("div", { children: [_jsx("span", { children: "20mm aggregate" }), _jsxs("strong", { children: [concreteAggregateVolumeM3.toFixed(2), " m\u00B3"] })] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("span", { children: selectedAggregate.label }), _jsxs("strong", { children: [volumeM3.toFixed(2), " m\u00B3"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Approx. weight" }), _jsxs("strong", { children: [(volumeM3 * selectedAggregate.density).toFixed(2), " t"] })] }), _jsx("div", {})] })) }), _jsx("p", { className: "materials-calculator__note", children: group === "concrete"
99
37
  ? "Based on a 1:2:4 mix. Confirm the mix design for structural work."
100
- : "Figures are estimates. Confirm the specification and supplier quantities before ordering." })] }), basketError && _jsx("p", { className: "results-empty-hint estimator-products__error", children: basketError }), _jsxs("div", { className: "materials-calculator__actions", children: [_jsx("button", { type: "button", className: "cta-btn cta-btn-secondary estimator-actions__back", onClick: onBack, children: "Back" }), _jsx("button", { type: "button", className: "cta-btn estimator-actions__calculate", onClick: submit, disabled: volumeM3 <= 0 || adding, children: adding ? "Adding products…" : submitLabel })] })] })] }) }));
38
+ : "Figures are estimates. Confirm the specification and supplier quantities before ordering." })] }), _jsxs("div", { className: "materials-calculator__actions", children: [_jsx("button", { type: "button", className: "cta-btn cta-btn-secondary estimator-actions__back", onClick: onBack, children: "Back" }), _jsx("button", { type: "button", className: "cta-btn estimator-actions__calculate", onClick: () => onReview?.(requirements), disabled: volumeM3 <= 0, children: submitLabel })] })] })] }) }));
101
39
  }
@@ -1,2 +1,2 @@
1
1
  import type { EstimatorProps } from "./registry";
2
- export default function PaintEstimator({ onBack }: EstimatorProps): import("react").JSX.Element;
2
+ export default function PaintEstimator({ onBack, onReview, submitLabel }: EstimatorProps): import("react").JSX.Element;
@@ -1,13 +1,14 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
+ import { fixedProductSearch } from "../../lib/estimators/fixedProducts";
4
5
  const PAINT_COVERAGE_M2_PER_LITRE = 10;
5
6
  const DOOR_AREA_M2 = 1.9 * 0.762;
6
7
  const DOUBLE_DOOR_AREA_M2 = 1.9 * 1.524;
7
8
  const emptyDimensions = () => ({ height: "", width: "" });
8
9
  const value = (input) => Number.parseFloat(input) || 0;
9
10
  const area = ({ height, width }) => value(height) * value(width);
10
- export default function PaintEstimator({ onBack }) {
11
+ export default function PaintEstimator({ onBack, onReview, submitLabel = "Add to basket" }) {
11
12
  const [walls, setWalls] = useState([emptyDimensions()]);
12
13
  const [excludedAreas, setExcludedAreas] = useState([emptyDimensions()]);
13
14
  const [doors, setDoors] = useState("0");
@@ -18,8 +19,12 @@ export default function PaintEstimator({ onBack }) {
18
19
  const doorArea = value(doors) * DOOR_AREA_M2 + value(doubleDoors) * DOUBLE_DOOR_AREA_M2;
19
20
  const paintableArea = Math.max(0, wallArea - excludedArea - doorArea);
20
21
  const litres = paintableArea * coats / PAINT_COVERAGE_M2_PER_LITRE;
22
+ // Asked for in litres; the basket rounds up to whole tins of the merchant's paint. Only offered
23
+ // where the merchant has a paint product set up - elsewhere this stays an estimate.
24
+ const requirement = { key: "paint-emulsion", label: "Matt emulsion, brilliant white", qty: Math.round(litres * 10) / 10, unit: "litres", query: "matt emulsion brilliant white" };
25
+ const canOrder = fixedProductSearch("paint", requirement) != null;
21
26
  const updateDimensions = (setter, index, key, nextValue) => {
22
27
  setter((items) => items.map((item, itemIndex) => itemIndex === index ? { ...item, [key]: nextValue } : item));
23
28
  };
24
- return (_jsx(_Fragment, { children: _jsxs("div", { className: "truss-estimator", children: [_jsxs("div", { className: "quick-order-hero", children: [_jsx("div", { className: "quick-order-eyebrow", children: "Estimating Tools" }), _jsx("h1", { children: "Paint Calculator" }), _jsx("p", { children: "Estimate how much paint you need for your walls, including multiple coats and excluded areas." })] }), _jsxs("div", { className: "truss-estimator__card paint-calculator", children: [_jsx("h2", { className: "truss-estimator__step-title", children: "Walls, exclusions and coats" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Enter each wall, subtract windows and doors, and choose your number of coats. Quantities assume a coverage of 10 m\u00B2 per litre." }), _jsxs("div", { className: "paint-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Wall dimensions" }), _jsx("p", { className: "paint-calculator__hint", children: "Enter the height and width of each wall you want to paint." }), _jsxs("div", { className: "paint-calculator__dimensions-header", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", { children: "Height (m)" }), _jsx("span", { children: "Width (m)" }), _jsx("span", {})] }), walls.map((wall, index) => (_jsxs("div", { className: "paint-calculator__row", children: [_jsxs("span", { className: "paint-calculator__row-label", children: ["Wall ", index + 1] }), _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { className: "sr-only", children: "Height (m)" }), _jsx("input", { "aria-label": `Wall ${index + 1} height in metres`, type: "number", min: "0", step: "0.01", value: wall.height, onChange: (event) => updateDimensions(setWalls, index, "height", event.target.value) })] }), _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { className: "sr-only", children: "Width (m)" }), _jsx("input", { "aria-label": `Wall ${index + 1} width in metres`, type: "number", min: "0", step: "0.01", value: wall.width, onChange: (event) => updateDimensions(setWalls, index, "width", event.target.value) })] }), walls.length > 1 ? _jsx("button", { type: "button", className: "paint-calculator__remove", onClick: () => setWalls((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: "Remove" }) : null] }, `wall-${index}`))), _jsx("button", { type: "button", className: "paint-calculator__add", onClick: () => setWalls((items) => [...items, emptyDimensions()]), children: "+ Add another wall" })] }), _jsxs("div", { className: "paint-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Areas to exclude" }), _jsx("p", { className: "paint-calculator__hint", children: "Add windows, sockets, skirting boards or other areas that will not be painted." }), _jsxs("div", { className: "paint-calculator__dimensions-header", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", { children: "Height (m)" }), _jsx("span", { children: "Width (m)" }), _jsx("span", {})] }), excludedAreas.map((excluded, index) => (_jsxs("div", { className: "paint-calculator__row", children: [_jsxs("span", { className: "paint-calculator__row-label", children: ["Area ", index + 1] }), _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { className: "sr-only", children: "Height (m)" }), _jsx("input", { "aria-label": `Area ${index + 1} height in metres`, type: "number", min: "0", step: "0.01", value: excluded.height, onChange: (event) => updateDimensions(setExcludedAreas, index, "height", event.target.value) })] }), _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { className: "sr-only", children: "Width (m)" }), _jsx("input", { "aria-label": `Area ${index + 1} width in metres`, type: "number", min: "0", step: "0.01", value: excluded.width, onChange: (event) => updateDimensions(setExcludedAreas, index, "width", event.target.value) })] }), excludedAreas.length > 1 ? _jsx("button", { type: "button", className: "paint-calculator__remove", onClick: () => setExcludedAreas((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: "Remove" }) : null] }, `excluded-${index}`))), _jsx("button", { type: "button", className: "paint-calculator__add", onClick: () => setExcludedAreas((items) => [...items, emptyDimensions()]), children: "+ Add another area" })] }), _jsxs("div", { className: "paint-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Doors and coats" }), _jsx("p", { className: "paint-calculator__hint", children: "Enter the number of standard single and double doors to leave unpainted. Door sizes are estimated automatically." }), _jsxs("div", { className: "truss-estimator__field-grid paint-calculator__small-fields", children: [_jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Doors" }), _jsx("input", { type: "number", min: "0", step: "1", value: doors, onChange: (event) => setDoors(event.target.value) })] }), _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Double doors" }), _jsx("input", { type: "number", min: "0", step: "1", value: doubleDoors, onChange: (event) => setDoubleDoors(event.target.value) })] })] }), _jsxs("div", { className: "paint-calculator__coat-field", children: [_jsx("p", { className: "paint-calculator__hint", children: "Most walls need 2 coats for an even finish. Use 3 coats when changing colour significantly or covering a darker wall." }), _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Number of coats" }), _jsxs("select", { value: coats, onChange: (event) => setCoats(Number(event.target.value)), children: [_jsx("option", { value: "1", children: "1 coat" }), _jsx("option", { value: "2", children: "2 coats" }), _jsx("option", { value: "3", children: "3 coats" })] })] })] })] }), _jsxs("div", { className: "paint-calculator__totals", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Totals" }), _jsxs("div", { className: "paint-calculator__result-grid", children: [_jsxs("div", { children: [_jsx("span", { children: "Paintable area" }), _jsxs("strong", { children: [paintableArea.toFixed(2), " m\u00B2"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Coverage" }), _jsxs("strong", { children: [coats, " ", coats === 1 ? "coat" : "coats"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Paint required" }), _jsxs("strong", { children: [litres.toFixed(1), " litres"] })] })] }), _jsx("p", { className: "paint-calculator__note", children: "Based on paint coverage of 10 m\u00B2 per litre. Decorative paint is not currently stocked in this catalogue, so this remains an estimate only." })] }), _jsxs("div", { className: "paint-calculator__actions", children: [_jsx("button", { type: "button", className: "cta-btn cta-btn-secondary estimator-actions__back", onClick: onBack, children: "Back" }), _jsx("button", { type: "button", className: "cta-btn estimator-actions__calculate", disabled: true, children: "Paint products unavailable" })] })] })] }) }));
29
+ return (_jsx(_Fragment, { children: _jsxs("div", { className: "truss-estimator", children: [_jsxs("div", { className: "quick-order-hero", children: [_jsx("div", { className: "quick-order-eyebrow", children: "Estimating Tools" }), _jsx("h1", { children: "Paint Calculator" }), _jsx("p", { children: "Estimate how much paint you need for your walls, including multiple coats and excluded areas." })] }), _jsxs("div", { className: "truss-estimator__card paint-calculator", children: [_jsx("h2", { className: "truss-estimator__step-title", children: "Walls, exclusions and coats" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Enter each wall, subtract windows and doors, and choose your number of coats. Quantities assume a coverage of 10 m\u00B2 per litre." }), _jsxs("div", { className: "paint-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Wall dimensions" }), _jsx("p", { className: "paint-calculator__hint", children: "Enter the height and width of each wall you want to paint." }), _jsxs("div", { className: "paint-calculator__dimensions-header", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", { children: "Height (m)" }), _jsx("span", { children: "Width (m)" }), _jsx("span", {})] }), walls.map((wall, index) => (_jsxs("div", { className: "paint-calculator__row", children: [_jsxs("span", { className: "paint-calculator__row-label", children: ["Wall ", index + 1] }), _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { className: "sr-only", children: "Height (m)" }), _jsx("input", { "aria-label": `Wall ${index + 1} height in metres`, type: "number", min: "0", step: "0.01", value: wall.height, onChange: (event) => updateDimensions(setWalls, index, "height", event.target.value) })] }), _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { className: "sr-only", children: "Width (m)" }), _jsx("input", { "aria-label": `Wall ${index + 1} width in metres`, type: "number", min: "0", step: "0.01", value: wall.width, onChange: (event) => updateDimensions(setWalls, index, "width", event.target.value) })] }), walls.length > 1 ? _jsx("button", { type: "button", className: "paint-calculator__remove", onClick: () => setWalls((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: "Remove" }) : null] }, `wall-${index}`))), _jsx("button", { type: "button", className: "paint-calculator__add", onClick: () => setWalls((items) => [...items, emptyDimensions()]), children: "+ Add another wall" })] }), _jsxs("div", { className: "paint-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Areas to exclude" }), _jsx("p", { className: "paint-calculator__hint", children: "Add windows, sockets, skirting boards or other areas that will not be painted." }), _jsxs("div", { className: "paint-calculator__dimensions-header", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", { children: "Height (m)" }), _jsx("span", { children: "Width (m)" }), _jsx("span", {})] }), excludedAreas.map((excluded, index) => (_jsxs("div", { className: "paint-calculator__row", children: [_jsxs("span", { className: "paint-calculator__row-label", children: ["Area ", index + 1] }), _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { className: "sr-only", children: "Height (m)" }), _jsx("input", { "aria-label": `Area ${index + 1} height in metres`, type: "number", min: "0", step: "0.01", value: excluded.height, onChange: (event) => updateDimensions(setExcludedAreas, index, "height", event.target.value) })] }), _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { className: "sr-only", children: "Width (m)" }), _jsx("input", { "aria-label": `Area ${index + 1} width in metres`, type: "number", min: "0", step: "0.01", value: excluded.width, onChange: (event) => updateDimensions(setExcludedAreas, index, "width", event.target.value) })] }), excludedAreas.length > 1 ? _jsx("button", { type: "button", className: "paint-calculator__remove", onClick: () => setExcludedAreas((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: "Remove" }) : null] }, `excluded-${index}`))), _jsx("button", { type: "button", className: "paint-calculator__add", onClick: () => setExcludedAreas((items) => [...items, emptyDimensions()]), children: "+ Add another area" })] }), _jsxs("div", { className: "paint-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Doors and coats" }), _jsx("p", { className: "paint-calculator__hint", children: "Enter the number of standard single and double doors to leave unpainted. Door sizes are estimated automatically." }), _jsxs("div", { className: "truss-estimator__field-grid paint-calculator__small-fields", children: [_jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Doors" }), _jsx("input", { type: "number", min: "0", step: "1", value: doors, onChange: (event) => setDoors(event.target.value) })] }), _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Double doors" }), _jsx("input", { type: "number", min: "0", step: "1", value: doubleDoors, onChange: (event) => setDoubleDoors(event.target.value) })] })] }), _jsxs("div", { className: "paint-calculator__coat-field", children: [_jsx("p", { className: "paint-calculator__hint", children: "Most walls need 2 coats for an even finish. Use 3 coats when changing colour significantly or covering a darker wall." }), _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Number of coats" }), _jsxs("select", { value: coats, onChange: (event) => setCoats(Number(event.target.value)), children: [_jsx("option", { value: "1", children: "1 coat" }), _jsx("option", { value: "2", children: "2 coats" }), _jsx("option", { value: "3", children: "3 coats" })] })] })] })] }), _jsxs("div", { className: "paint-calculator__totals", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Totals" }), _jsxs("div", { className: "paint-calculator__result-grid", children: [_jsxs("div", { children: [_jsx("span", { children: "Paintable area" }), _jsxs("strong", { children: [paintableArea.toFixed(2), " m\u00B2"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Coverage" }), _jsxs("strong", { children: [coats, " ", coats === 1 ? "coat" : "coats"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Paint required" }), _jsxs("strong", { children: [litres.toFixed(1), " litres"] })] })] }), _jsx("p", { className: "paint-calculator__note", children: canOrder ? "Based on paint coverage of 10 m² per litre. Ordered as brilliant white matt emulsion, rounded up to whole tins." : "Based on paint coverage of 10 m² per litre. Decorative paint is not currently stocked in this catalogue, so this remains an estimate only." })] }), _jsxs("div", { className: "paint-calculator__actions", children: [_jsx("button", { type: "button", className: "cta-btn cta-btn-secondary estimator-actions__back", onClick: onBack, children: "Back" }), canOrder ? (_jsx("button", { type: "button", className: "cta-btn estimator-actions__calculate", onClick: () => onReview?.([requirement]), disabled: litres <= 0, children: submitLabel })) : (_jsx("button", { type: "button", className: "cta-btn estimator-actions__calculate", disabled: true, children: "Paint products unavailable" }))] })] })] }) }));
25
30
  }