@metron-labs/quote-react 0.1.60 → 0.1.61
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.
- package/dist/components/estimators/ArchitraveEstimator.js +2 -1
- package/dist/components/estimators/DeckingEstimator.js +2 -1
- package/dist/components/estimators/FencingEstimator.js +10 -4
- package/dist/embed/quote.js +12 -12
- package/dist/lib/estimators/fixedProducts.d.ts +5 -0
- package/dist/lib/estimators/fixedProducts.js +11 -2
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
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 { offersOption } from "../../lib/estimators/fixedProducts";
|
|
4
5
|
const PROFILES = [
|
|
5
6
|
{ id: "ogee", label: "Ogee", blurb: "Traditional curved profile" },
|
|
6
7
|
{ id: "torus", label: "Torus", blurb: "Soft rounded detail" },
|
|
@@ -46,5 +47,5 @@ export default function ArchitraveEstimator({ onBack, onReview, submitLabel = "A
|
|
|
46
47
|
},
|
|
47
48
|
];
|
|
48
49
|
const openingRow = (label, opening, onChange, onRemove) => (_jsxs("div", { className: "architrave-calculator__opening-row", children: [_jsx("span", { children: label }), _jsx("input", { "aria-label": `${label} quantity`, type: "number", min: "0", step: "1", value: opening.quantity, onChange: (event) => onChange("quantity", event.target.value) }), _jsx("input", { "aria-label": `${label} width`, type: "number", min: "0", step: "0.01", value: opening.width, onChange: (event) => onChange("width", event.target.value) }), _jsx("input", { "aria-label": `${label} height`, type: "number", min: "0", step: "0.01", value: opening.height, onChange: (event) => onChange("height", event.target.value) }), onRemove ? _jsx("button", { type: "button", className: "paint-calculator__remove", onClick: onRemove, children: "Remove" }) : null] }));
|
|
49
|
-
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: "Architrave Calculator" }), _jsx("p", { children: "Estimate architrave lengths and board quantities for door openings." })] }), _jsxs("div", { className: "truss-estimator__card architrave-calculator", children: [_jsx("h2", { className: "truss-estimator__step-title", children: "Measure your openings" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Enter each door or window opening. Mitred corners are allowed for, and board lengths are worked out from the net run." }), _jsxs("div", { className: "architrave-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Door openings" }), _jsx("p", { className: "architrave-calculator__hint", children: "Each opening is calculated as two side pieces plus one head piece." }), _jsxs("div", { className: "architrave-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: "Metres" }), _jsx("button", { type: "button", className: unit === "ft" ? "is-selected" : "", onClick: () => setUnit("ft"), children: "Feet" })] })] }), _jsxs("div", { className: "architrave-calculator__dimensions-header", children: [_jsx("span", { children: "Opening" }), _jsx("span", { children: "Qty" }), _jsxs("span", { children: ["Width (", unit, ")"] }), _jsxs("span", { children: ["Height (", unit, ")"] }), _jsx("span", {})] }), openingRow("Single doors", singleDoor, (key, value) => updateOpening(setSingleDoor, key, value)), openingRow("Double doors", doubleDoor, (key, value) => updateOpening(setDoubleDoor, key, value)), alternativeDoors.map((opening, index) => openingRow(`Alternative door ${index + 1}`, opening, (key, value) => setAlternativeDoors((items) => items.map((item, itemIndex) => itemIndex === index ? { ...item, [key]: value } : item)), () => setAlternativeDoors((items) => items.filter((_, itemIndex) => itemIndex !== index)))), _jsx("button", { type: "button", className: "paint-calculator__add", onClick: () => setAlternativeDoors((items) => [...items, emptyOpening()]), children: "+ Add alternative-sized door" })] }), _jsxs("div", { className: "architrave-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Standard profile" }), _jsx("p", { className: "architrave-calculator__hint", children: "Choose the profile you plan to use. Length calculations are the same for each profile." }), _jsx("div", { className: "architrave-calculator__profiles", children: PROFILES.map((option) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${profile === option.id ? " is-selected" : ""}`, onClick: () => setProfile(option.id), children: [_jsx(ProfileDiagram, { profile: option.id }), _jsx("div", { className: "truss-estimator__option-title", children: option.label }), _jsx("div", { className: "truss-estimator__option-copy", children: option.blurb })] }, option.id))) })] }), _jsxs("div", { className: "architrave-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Board and waste" }), _jsx("div", { className: "architrave-calculator__options", children: _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Board length (m)" }), _jsx("input", { value: "4.40 m", disabled: true })] }) }), _jsx("p", { className: "architrave-calculator__hint", children: "Waste allows for mitred corners, cuts and damaged lengths." })] }), _jsxs("div", { className: "architrave-calculator__totals", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Totals" }), _jsxs("div", { className: "architrave-calculator__result-grid", children: [_jsxs("div", { children: [_jsx("span", { children: "Architrave needed" }), _jsxs("strong", { children: [baseLengthM.toFixed(2), " m"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Board lengths" }), _jsx("strong", { children: boardCount })] })] })] }), _jsxs("div", { className: "architrave-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: baseLengthM <= 0, children: submitLabel })] })] })] }) }));
|
|
50
|
+
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: "Architrave Calculator" }), _jsx("p", { children: "Estimate architrave lengths and board quantities for door openings." })] }), _jsxs("div", { className: "truss-estimator__card architrave-calculator", children: [_jsx("h2", { className: "truss-estimator__step-title", children: "Measure your openings" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Enter each door or window opening. Mitred corners are allowed for, and board lengths are worked out from the net run." }), _jsxs("div", { className: "architrave-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Door openings" }), _jsx("p", { className: "architrave-calculator__hint", children: "Each opening is calculated as two side pieces plus one head piece." }), _jsxs("div", { className: "architrave-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: "Metres" }), _jsx("button", { type: "button", className: unit === "ft" ? "is-selected" : "", onClick: () => setUnit("ft"), children: "Feet" })] })] }), _jsxs("div", { className: "architrave-calculator__dimensions-header", children: [_jsx("span", { children: "Opening" }), _jsx("span", { children: "Qty" }), _jsxs("span", { children: ["Width (", unit, ")"] }), _jsxs("span", { children: ["Height (", unit, ")"] }), _jsx("span", {})] }), openingRow("Single doors", singleDoor, (key, value) => updateOpening(setSingleDoor, key, value)), openingRow("Double doors", doubleDoor, (key, value) => updateOpening(setDoubleDoor, key, value)), alternativeDoors.map((opening, index) => openingRow(`Alternative door ${index + 1}`, opening, (key, value) => setAlternativeDoors((items) => items.map((item, itemIndex) => itemIndex === index ? { ...item, [key]: value } : item)), () => setAlternativeDoors((items) => items.filter((_, itemIndex) => itemIndex !== index)))), _jsx("button", { type: "button", className: "paint-calculator__add", onClick: () => setAlternativeDoors((items) => [...items, emptyOpening()]), children: "+ Add alternative-sized door" })] }), _jsxs("div", { className: "architrave-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Standard profile" }), _jsx("p", { className: "architrave-calculator__hint", children: "Choose the profile you plan to use. Length calculations are the same for each profile." }), _jsx("div", { className: "architrave-calculator__profiles", children: PROFILES.filter((option) => offersOption("architrave", `architrave-${option.id}`)).map((option) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${profile === option.id ? " is-selected" : ""}`, onClick: () => setProfile(option.id), children: [_jsx(ProfileDiagram, { profile: option.id }), _jsx("div", { className: "truss-estimator__option-title", children: option.label }), _jsx("div", { className: "truss-estimator__option-copy", children: option.blurb })] }, option.id))) })] }), _jsxs("div", { className: "architrave-calculator__section", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Board and waste" }), _jsx("div", { className: "architrave-calculator__options", children: _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Board length (m)" }), _jsx("input", { value: "4.40 m", disabled: true })] }) }), _jsx("p", { className: "architrave-calculator__hint", children: "Waste allows for mitred corners, cuts and damaged lengths." })] }), _jsxs("div", { className: "architrave-calculator__totals", children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Totals" }), _jsxs("div", { className: "architrave-calculator__result-grid", children: [_jsxs("div", { children: [_jsx("span", { children: "Architrave needed" }), _jsxs("strong", { children: [baseLengthM.toFixed(2), " m"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Board lengths" }), _jsx("strong", { children: boardCount })] })] })] }), _jsxs("div", { className: "architrave-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: baseLengthM <= 0, children: submitLabel })] })] })] }) }));
|
|
50
51
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useRef, useState } from "react";
|
|
4
4
|
import { assetUrl } from "../../lib/assets";
|
|
5
|
+
import { offersOption } from "../../lib/estimators/fixedProducts";
|
|
5
6
|
import ForwardArrow from "../ForwardArrow";
|
|
6
7
|
const CANVAS_WIDTH_M = 12;
|
|
7
8
|
const CANVAS_HEIGHT_M = 8;
|
|
@@ -272,7 +273,7 @@ export default function DeckingEstimator({ onBack, onReview, submitLabel = "Add
|
|
|
272
273
|
else
|
|
273
274
|
setStepIndex((value) => value + 1);
|
|
274
275
|
};
|
|
275
|
-
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: "Decking Calculator" }), _jsx("p", { children: "Estimate timber boards, joists and screws for your deck." })] }), _jsxs("div", { className: "truss-estimator__card", children: [_jsxs("div", { className: "truss-estimator__body", children: [currentStep === "range" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Choose your timber decking" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Both options are fixed, orderable pressure-treated timber boards." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: Object.entries(CONFIG.ranges).map(([id, range]) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${state.range === id ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, range: id })), children: [_jsx("div", { className: "truss-estimator__option-title", children: range.label }), _jsx("div", { className: "truss-estimator__option-copy", children: range.blurb }), _jsxs("div", { className: "truss-estimator__option-copy", children: [range.boardLengthM, "m boards"] })] }, id))) })] })) : null, currentStep === "grid" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Tell us your deck size" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Start with a quick rectangle estimate, or draw an irregular deck for a more precise result." }), _jsxs("div", { className: "truss-estimator__toggle-row", children: [_jsx("button", { type: "button", className: `truss-estimator__toggle${state.deckSizeMode === "quick" ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, deckSizeMode: "quick" })), children: "Quick estimate" }), _jsx("button", { type: "button", className: `truss-estimator__toggle${state.deckSizeMode === "draw" ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, deckSizeMode: "draw", drawMode: "outline" })), children: "Draw your space" })] }), state.deckSizeMode === "quick" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__field-grid", children: [_jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Length (m)" }), _jsx("input", { type: "number", inputMode: "decimal", min: "0.1", step: "0.1", value: state.quickLengthM, onChange: (event) => setState((value) => ({ ...value, quickLengthM: event.target.value })) })] }), _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Width (m)" }), _jsx("input", { type: "number", inputMode: "decimal", min: "0.1", step: "0.1", value: state.quickWidthM, onChange: (event) => setState((value) => ({ ...value, quickWidthM: event.target.value })) })] })] }), _jsx("div", { className: "truss-estimator__grid-hint", children: "For L-shaped decks, cut-outs, or sides against a wall, use Draw your space." }), hasQuickDimensions ? (_jsxs("div", { className: "truss-estimator__grid-stats", children: [_jsxs("div", { children: [(quickLengthM * quickWidthM).toFixed(2), " m\u00B2 deck"] }), _jsxs("div", { children: [(2 * (quickLengthM + quickWidthM)).toFixed(2), " m perimeter"] })] })) : null] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Draw your deck" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Click to add corners, then close the outline. Drag any handle to refine it." }), _jsxs("div", { className: "deck-outline__toolbar", children: [state.isOutlineClosed ? (_jsxs("div", { className: "deck-outline__summary", children: [_jsxs("div", { children: [_jsx("span", { children: "Deck area" }), _jsxs("strong", { children: [drawingArea.toFixed(2), " m\u00B2"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Perimeter" }), _jsxs("strong", { children: [drawingPerimeter.toFixed(2), " m"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Wall edges" }), _jsx("strong", { children: state.wallSegments.size > 0 ? state.wallSegments.size : "None" })] }), _jsxs("div", { children: [_jsx("span", { children: "Visible edge" }), _jsxs("strong", { children: [visibleDrawingEdgeLength.toFixed(2), " m"] })] })] })) : null, _jsxs("div", { className: "deck-outline__actions", children: [_jsx("button", { type: "button", className: "deck-outline__action", onClick: () => setState((value) => ({ ...value, points: value.points.slice(0, -1) })), disabled: state.points.length === 0 || state.isOutlineClosed, children: "Undo" }), _jsx("button", { type: "button", className: "deck-outline__action", onClick: () => {
|
|
276
|
+
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: "Decking Calculator" }), _jsx("p", { children: "Estimate timber boards, joists and screws for your deck." })] }), _jsxs("div", { className: "truss-estimator__card", children: [_jsxs("div", { className: "truss-estimator__body", children: [currentStep === "range" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Choose your timber decking" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Both options are fixed, orderable pressure-treated timber boards." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: Object.entries(CONFIG.ranges).filter(([id]) => offersOption("decking", `boards-${id}`)).map(([id, range]) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${state.range === id ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, range: id })), children: [_jsx("div", { className: "truss-estimator__option-title", children: range.label }), _jsx("div", { className: "truss-estimator__option-copy", children: range.blurb }), _jsxs("div", { className: "truss-estimator__option-copy", children: [range.boardLengthM, "m boards"] })] }, id))) })] })) : null, currentStep === "grid" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Tell us your deck size" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Start with a quick rectangle estimate, or draw an irregular deck for a more precise result." }), _jsxs("div", { className: "truss-estimator__toggle-row", children: [_jsx("button", { type: "button", className: `truss-estimator__toggle${state.deckSizeMode === "quick" ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, deckSizeMode: "quick" })), children: "Quick estimate" }), _jsx("button", { type: "button", className: `truss-estimator__toggle${state.deckSizeMode === "draw" ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, deckSizeMode: "draw", drawMode: "outline" })), children: "Draw your space" })] }), state.deckSizeMode === "quick" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__field-grid", children: [_jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Length (m)" }), _jsx("input", { type: "number", inputMode: "decimal", min: "0.1", step: "0.1", value: state.quickLengthM, onChange: (event) => setState((value) => ({ ...value, quickLengthM: event.target.value })) })] }), _jsxs("label", { className: "truss-estimator__field", children: [_jsx("span", { children: "Width (m)" }), _jsx("input", { type: "number", inputMode: "decimal", min: "0.1", step: "0.1", value: state.quickWidthM, onChange: (event) => setState((value) => ({ ...value, quickWidthM: event.target.value })) })] })] }), _jsx("div", { className: "truss-estimator__grid-hint", children: "For L-shaped decks, cut-outs, or sides against a wall, use Draw your space." }), hasQuickDimensions ? (_jsxs("div", { className: "truss-estimator__grid-stats", children: [_jsxs("div", { children: [(quickLengthM * quickWidthM).toFixed(2), " m\u00B2 deck"] }), _jsxs("div", { children: [(2 * (quickLengthM + quickWidthM)).toFixed(2), " m perimeter"] })] })) : null] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Draw your deck" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Click to add corners, then close the outline. Drag any handle to refine it." }), _jsxs("div", { className: "deck-outline__toolbar", children: [state.isOutlineClosed ? (_jsxs("div", { className: "deck-outline__summary", children: [_jsxs("div", { children: [_jsx("span", { children: "Deck area" }), _jsxs("strong", { children: [drawingArea.toFixed(2), " m\u00B2"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Perimeter" }), _jsxs("strong", { children: [drawingPerimeter.toFixed(2), " m"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Wall edges" }), _jsx("strong", { children: state.wallSegments.size > 0 ? state.wallSegments.size : "None" })] }), _jsxs("div", { children: [_jsx("span", { children: "Visible edge" }), _jsxs("strong", { children: [visibleDrawingEdgeLength.toFixed(2), " m"] })] })] })) : null, _jsxs("div", { className: "deck-outline__actions", children: [_jsx("button", { type: "button", className: "deck-outline__action", onClick: () => setState((value) => ({ ...value, points: value.points.slice(0, -1) })), disabled: state.points.length === 0 || state.isOutlineClosed, children: "Undo" }), _jsx("button", { type: "button", className: "deck-outline__action", onClick: () => {
|
|
276
277
|
setDrawCursorPoint(null);
|
|
277
278
|
setState((value) => ({ ...value, points: [], isOutlineClosed: false, wallSegments: new Set(), drawMode: "outline", drawError: "" }));
|
|
278
279
|
}, disabled: state.points.length === 0, children: "Clear" }), _jsx("button", { type: "button", className: "deck-outline__action", onClick: () => setState((value) => ({
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useRef, useState } from "react";
|
|
4
4
|
import { assetUrl } from "../../lib/assets";
|
|
5
|
+
import { offersOption } from "../../lib/estimators/fixedProducts";
|
|
5
6
|
import ForwardArrow from "../ForwardArrow";
|
|
6
7
|
const GRID_SIZE = 12;
|
|
7
8
|
const CELL_PX = 30;
|
|
@@ -31,6 +32,11 @@ const CONFIG = {
|
|
|
31
32
|
},
|
|
32
33
|
};
|
|
33
34
|
const STEPS = ["range", "height", "gate", "posts"];
|
|
35
|
+
// Fences 1.2m and lower use 6ft posts; taller ones 8ft.
|
|
36
|
+
const postLengthFor = (height) => (Number(height) <= 1.2 ? "6" : "8");
|
|
37
|
+
// Only the panels and posts the merchant stocks are offered (see offersOption).
|
|
38
|
+
const heightOffered = (range, height) => offersOption("fencing", `panels-${range ?? "boundary"}-${height}`);
|
|
39
|
+
const mountingOffered = (mounting, height) => !height || offersOption("fencing", mounting === "concrete" ? `concrete-intermediate-${postLengthFor(height)}` : `timber-post-${postLengthFor(height)}`);
|
|
34
40
|
const INITIAL_STATE = {
|
|
35
41
|
range: null,
|
|
36
42
|
colour: null,
|
|
@@ -199,7 +205,7 @@ export default function FencingEstimator({ onBack, onReview, submitLabel = "Add
|
|
|
199
205
|
};
|
|
200
206
|
const results = computeResults();
|
|
201
207
|
const gateLabel = state.gateType && state.gateType !== "none" ? CONFIG.gate[state.gateType].label : null;
|
|
202
|
-
const postLength =
|
|
208
|
+
const postLength = postLengthFor(results.height);
|
|
203
209
|
const requirements = results
|
|
204
210
|
? [
|
|
205
211
|
{ key: `panels-${state.range ?? "boundary"}-${results.height}`, label: `${results.range.label} panels (${results.height}m)`, qty: results.panelsCount, unit: "each", query: "fence panel", category: "fencing" },
|
|
@@ -419,7 +425,7 @@ export default function FencingEstimator({ onBack, onReview, submitLabel = "Add
|
|
|
419
425
|
setStepIndex((value) => value + 1);
|
|
420
426
|
};
|
|
421
427
|
const perimeterEdges = getPerimeterEdges(state.grid);
|
|
422
|
-
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: "Fencing Calculator" }), _jsx("p", { children: "Estimate panels, posts and cost for your fence line in a few steps." })] }), _jsxs("div", { className: "truss-estimator__card", children: [_jsxs("div", { className: "truss-estimator__body", children: [currentStep === "range" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Choose your fence panels" }), _jsx("p", { className: "truss-estimator__step-sub", children: "The calculator uses fixed, orderable timber panels from the catalogue." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: Object.entries(CONFIG.ranges).map(([id, range]) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${state.range === id ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, range: id })), children: [_jsx("div", { className: "truss-estimator__option-title", children: range.label }), _jsx("div", { className: "truss-estimator__option-copy", children: range.blurb })] }, id))) })] })) : null, currentStep === "range" ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Measure your fence line" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Use a quick total, or draw the boundary to mark existing edges that do not need new fencing." }), _jsxs("div", { className: "truss-estimator__toggle-row", children: [_jsx("button", { type: "button", className: `truss-estimator__toggle${state.boundaryMode === "quick" ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, boundaryMode: "quick" })), children: "Quick estimate" }), _jsx("button", { type: "button", className: `truss-estimator__toggle${state.boundaryMode === "draw" ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, boundaryMode: "draw" })), children: "Draw your boundary" })] }), state.boundaryMode === "quick" ? (_jsxs("label", { className: "truss-estimator__field truss-estimator__field--compact fence-boundary__quick-field", children: [_jsx("span", { children: "Total fence length (m)" }), _jsx("input", { type: "number", min: "0", step: "0.1", value: state.quickLengthM, onChange: (event) => setState((value) => ({ ...value, quickLengthM: event.target.value })) })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "fence-boundary__toolbar", children: _jsxs("div", { className: "fence-boundary__actions", children: [_jsx("button", { type: "button", className: "deck-outline__action", onClick: () => setState((value) => ({ ...value, boundaryPoints: value.boundaryPoints.slice(0, -1), boundaryClosed: false, excludedBoundaryEdges: new Set() })), disabled: state.boundaryPoints.length === 0 || state.boundaryClosed, children: "Undo" }), _jsx("button", { type: "button", className: "deck-outline__action", onClick: () => {
|
|
428
|
+
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: "Fencing Calculator" }), _jsx("p", { children: "Estimate panels, posts and cost for your fence line in a few steps." })] }), _jsxs("div", { className: "truss-estimator__card", children: [_jsxs("div", { className: "truss-estimator__body", children: [currentStep === "range" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Choose your fence panels" }), _jsx("p", { className: "truss-estimator__step-sub", children: "The calculator uses fixed, orderable timber panels from the catalogue." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: Object.entries(CONFIG.ranges).map(([id, range]) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${state.range === id ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, range: id, height: value.height && heightOffered(id, value.height) ? value.height : null })), children: [_jsx("div", { className: "truss-estimator__option-title", children: range.label }), _jsx("div", { className: "truss-estimator__option-copy", children: range.blurb })] }, id))) })] })) : null, currentStep === "range" ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "truss-estimator__section-heading", children: "Measure your fence line" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Use a quick total, or draw the boundary to mark existing edges that do not need new fencing." }), _jsxs("div", { className: "truss-estimator__toggle-row", children: [_jsx("button", { type: "button", className: `truss-estimator__toggle${state.boundaryMode === "quick" ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, boundaryMode: "quick" })), children: "Quick estimate" }), _jsx("button", { type: "button", className: `truss-estimator__toggle${state.boundaryMode === "draw" ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, boundaryMode: "draw" })), children: "Draw your boundary" })] }), state.boundaryMode === "quick" ? (_jsxs("label", { className: "truss-estimator__field truss-estimator__field--compact fence-boundary__quick-field", children: [_jsx("span", { children: "Total fence length (m)" }), _jsx("input", { type: "number", min: "0", step: "0.1", value: state.quickLengthM, onChange: (event) => setState((value) => ({ ...value, quickLengthM: event.target.value })) })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "fence-boundary__toolbar", children: _jsxs("div", { className: "fence-boundary__actions", children: [_jsx("button", { type: "button", className: "deck-outline__action", onClick: () => setState((value) => ({ ...value, boundaryPoints: value.boundaryPoints.slice(0, -1), boundaryClosed: false, excludedBoundaryEdges: new Set() })), disabled: state.boundaryPoints.length === 0 || state.boundaryClosed, children: "Undo" }), _jsx("button", { type: "button", className: "deck-outline__action", onClick: () => {
|
|
423
429
|
setBoundaryPan({ x: 0, y: 0 });
|
|
424
430
|
setBoundaryCursorPoint(null);
|
|
425
431
|
setState((value) => ({ ...value, boundaryPoints: [], boundaryClosed: false, excludedBoundaryEdges: new Set() }));
|
|
@@ -487,7 +493,7 @@ export default function FencingEstimator({ onBack, onReview, submitLabel = "Add
|
|
|
487
493
|
next.add(edge.key);
|
|
488
494
|
return { ...value, excludedEdges: next };
|
|
489
495
|
}) }, edge.key));
|
|
490
|
-
}) })) : null] }) }), state.gridMode === "walls" ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "truss-estimator__grid-hint", children: "Click an edge on the shape above to toggle it." }), _jsxs("div", { className: "truss-estimator__legend", children: [_jsxs("span", { className: "truss-estimator__legend-item", children: [_jsx("svg", { className: "truss-estimator__legend-line", viewBox: "0 0 26 10", "aria-hidden": "true", children: _jsx("line", { x1: "2", y1: "5", x2: "24", y2: "5", stroke: "var(--accent)", strokeWidth: "4", strokeLinecap: "round" }) }), "Needs new fencing"] }), _jsxs("span", { className: "truss-estimator__legend-item", children: [_jsx("svg", { className: "truss-estimator__legend-line", viewBox: "0 0 26 10", "aria-hidden": "true", children: _jsx("line", { x1: "2", y1: "5", x2: "24", y2: "5", stroke: "#9aa3b5", strokeWidth: "4", strokeLinecap: "round", strokeDasharray: "3 3" }) }), "Existing boundary"] })] })] })) : (_jsx("div", { className: "truss-estimator__grid-hint", children: "Once you have drawn the plot, switch to boundary mode to exclude any sides that do not need a new fence." })), _jsxs("div", { className: "truss-estimator__grid-stats", children: [_jsxs("div", { children: [state.grid.size, " m\u00B2 plot"] }), state.gridMode === "walls" ? (_jsxs(_Fragment, { children: [_jsxs("div", { children: [results.totalEdgeM, " m fence run"] }), _jsxs("div", { children: [results.cornerPostCount, " corner/end posts"] })] })) : null] })] })) : null, currentStep === "height" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Fence height" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Select the nearest available panel height." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--pitch", children: CONFIG.heights.map((height) => (_jsx("button", { type: "button", className: `truss-estimator__visual-card${state.height === height ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, height })), children: _jsx("img", { className: "truss-estimator__visual-card-img truss-estimator__visual-card-img--tall", src: assetUrl(`/fencing/height/${height}.webp`), alt: "" }) }, height))) }), _jsx("div", { className: "truss-estimator__section-heading", children: "Gravel board?" }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: [
|
|
496
|
+
}) })) : null] }) }), state.gridMode === "walls" ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "truss-estimator__grid-hint", children: "Click an edge on the shape above to toggle it." }), _jsxs("div", { className: "truss-estimator__legend", children: [_jsxs("span", { className: "truss-estimator__legend-item", children: [_jsx("svg", { className: "truss-estimator__legend-line", viewBox: "0 0 26 10", "aria-hidden": "true", children: _jsx("line", { x1: "2", y1: "5", x2: "24", y2: "5", stroke: "var(--accent)", strokeWidth: "4", strokeLinecap: "round" }) }), "Needs new fencing"] }), _jsxs("span", { className: "truss-estimator__legend-item", children: [_jsx("svg", { className: "truss-estimator__legend-line", viewBox: "0 0 26 10", "aria-hidden": "true", children: _jsx("line", { x1: "2", y1: "5", x2: "24", y2: "5", stroke: "#9aa3b5", strokeWidth: "4", strokeLinecap: "round", strokeDasharray: "3 3" }) }), "Existing boundary"] })] })] })) : (_jsx("div", { className: "truss-estimator__grid-hint", children: "Once you have drawn the plot, switch to boundary mode to exclude any sides that do not need a new fence." })), _jsxs("div", { className: "truss-estimator__grid-stats", children: [_jsxs("div", { children: [state.grid.size, " m\u00B2 plot"] }), state.gridMode === "walls" ? (_jsxs(_Fragment, { children: [_jsxs("div", { children: [results.totalEdgeM, " m fence run"] }), _jsxs("div", { children: [results.cornerPostCount, " corner/end posts"] })] })) : null] })] })) : null, currentStep === "height" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Fence height" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Select the nearest available panel height." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--pitch", children: CONFIG.heights.filter((height) => heightOffered(state.range, height)).map((height) => (_jsx("button", { type: "button", className: `truss-estimator__visual-card${state.height === height ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, height, mountingType: value.mountingType && mountingOffered(value.mountingType, height) ? value.mountingType : null })), children: _jsx("img", { className: "truss-estimator__visual-card-img truss-estimator__visual-card-img--tall", src: assetUrl(`/fencing/height/${height}.webp`), alt: "" }) }, height))) }), _jsx("div", { className: "truss-estimator__section-heading", children: "Gravel board?" }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: [
|
|
491
497
|
{ id: "no", label: "No gravel board", img: "/fencing/no-gravel-board.webp" },
|
|
492
498
|
{ id: "yes", label: "Add gravel board", img: "/fencing/gravel-board.webp" },
|
|
493
499
|
].map((option) => (_jsxs("button", { type: "button", className: `truss-estimator__visual-card truss-estimator__visual-card--wide${state.gravelBoard === option.id ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, gravelBoard: option.id })), children: [_jsx("img", { className: "truss-estimator__visual-card-img truss-estimator__visual-card-img--mid", src: assetUrl(option.img), alt: "" }), _jsx("span", { children: option.label })] }, option.id))) })] })) : null, currentStep === "gate" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Do you need a gate?" }), _jsx("p", { className: "truss-estimator__step-sub", children: "A gate opening replaces part of the panel run and adds a gate kit with heavy-duty posts." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: [
|
|
@@ -497,5 +503,5 @@ export default function FencingEstimator({ onBack, onReview, submitLabel = "Add
|
|
|
497
503
|
...value,
|
|
498
504
|
gateType: option.id,
|
|
499
505
|
gateWidthM: option.id === "none" ? "" : String(CONFIG.gate[option.id].widthDefault),
|
|
500
|
-
})), children: [_jsx("img", { className: "truss-estimator__visual-card-img truss-estimator__visual-card-img--mid", src: assetUrl(option.img), alt: "" }), _jsx("span", { children: option.label })] }, option.id))) }), state.gateType && state.gateType !== "none" ? (_jsxs("label", { className: "truss-estimator__field truss-estimator__field--compact", children: [_jsx("span", { children: "Gate width (m)" }), _jsx("input", { type: "text", inputMode: "decimal", value: state.gateWidthM, onChange: (event) => setState((value) => ({ ...value, gateWidthM: event.target.value })) })] })) : null] })) : null, currentStep === "posts" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Posts and fixing type" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Choose how the posts will be set." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: Object.entries(CONFIG.mounting).map(([id, mounting]) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${state.mountingType === id ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, mountingType: id })), children: [_jsx("div", { className: "truss-estimator__option-title", children: mounting.label }), _jsx("div", { className: "truss-estimator__option-copy", children: mounting.blurb })] }, id))) })] })) : null] }), _jsxs("div", { className: "truss-estimator__nav", children: [_jsx("button", { type: "button", className: "cta-btn cta-btn-secondary", style: { flex: "0 1 30%", justifyContent: "center", marginTop: 0 }, onClick: handleBack, children: "Back" }), _jsxs("button", { type: "button", className: "cta-btn", style: { flex: "0 1 70%", marginTop: 0 }, onClick: handleNext, disabled: !canContinue(), children: [_jsx("span", { children: nextLabel }), _jsx("span", { className: "arrow", children: _jsx(ForwardArrow, {}) })] })] })] })] }) }));
|
|
506
|
+
})), children: [_jsx("img", { className: "truss-estimator__visual-card-img truss-estimator__visual-card-img--mid", src: assetUrl(option.img), alt: "" }), _jsx("span", { children: option.label })] }, option.id))) }), state.gateType && state.gateType !== "none" ? (_jsxs("label", { className: "truss-estimator__field truss-estimator__field--compact", children: [_jsx("span", { children: "Gate width (m)" }), _jsx("input", { type: "text", inputMode: "decimal", value: state.gateWidthM, onChange: (event) => setState((value) => ({ ...value, gateWidthM: event.target.value })) })] })) : null] })) : null, currentStep === "posts" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "truss-estimator__step-label", children: ["Step ", stepIndex + 1, " of ", STEPS.length] }), _jsx("h2", { className: "truss-estimator__step-title", children: "Posts and fixing type" }), _jsx("p", { className: "truss-estimator__step-sub", children: "Choose how the posts will be set." }), _jsx("div", { className: "truss-estimator__option-grid truss-estimator__option-grid--two", children: Object.entries(CONFIG.mounting).filter(([id]) => mountingOffered(id, state.height)).map(([id, mounting]) => (_jsxs("button", { type: "button", className: `truss-estimator__option-card${state.mountingType === id ? " is-selected" : ""}`, onClick: () => setState((value) => ({ ...value, mountingType: id })), children: [_jsx("div", { className: "truss-estimator__option-title", children: mounting.label }), _jsx("div", { className: "truss-estimator__option-copy", children: mounting.blurb })] }, id))) })] })) : null] }), _jsxs("div", { className: "truss-estimator__nav", children: [_jsx("button", { type: "button", className: "cta-btn cta-btn-secondary", style: { flex: "0 1 30%", justifyContent: "center", marginTop: 0 }, onClick: handleBack, children: "Back" }), _jsxs("button", { type: "button", className: "cta-btn", style: { flex: "0 1 70%", marginTop: 0 }, onClick: handleNext, disabled: !canContinue(), children: [_jsx("span", { children: nextLabel }), _jsx("span", { className: "arrow", children: _jsx(ForwardArrow, {}) })] })] })] })] }) }));
|
|
501
507
|
}
|