diff --git a/client/src/components/StepOperator.jsx b/client/src/components/StepOperator.jsx
index e789515..d8ff208 100644
--- a/client/src/components/StepOperator.jsx
+++ b/client/src/components/StepOperator.jsx
@@ -10,8 +10,10 @@ const categoryLabels = {
export default function StepOperator({
operators,
selected,
+ armChoice,
optionalParts,
onSelect,
+ onSetArmChoice,
onToggleOptionalPart,
onNext,
}) {
@@ -92,6 +94,44 @@ export default function StepOperator({
))}
+ {selectedOp?.armOptions && selectedOp.armOptions.length > 0 && (
+
+
+ Select Arm Length
+
+
+ {selectedOp.armOptions.map((opt) => {
+ const isSelected = armChoice === opt.id;
+ return (
+
+ );
+ })}
+
+
+ )}
+
{selectedOp?.optionalParts && selectedOp.optionalParts.length > 0 && (
diff --git a/client/src/components/Wizard.jsx b/client/src/components/Wizard.jsx
index 9b9511f..d5a942b 100644
--- a/client/src/components/Wizard.jsx
+++ b/client/src/components/Wizard.jsx
@@ -7,6 +7,7 @@ import QuoteSummary from './QuoteSummary';
const initialState = {
step: 1,
operator: null,
+ armChoice: null,
optionalParts: [],
groundLoops: {
needed: false,
@@ -22,7 +23,9 @@ const initialState = {
function reducer(state, action) {
switch (action.type) {
case 'SELECT_OPERATOR':
- return { ...state, operator: action.payload, optionalParts: [], step: 2 };
+ return { ...state, operator: action.payload, armChoice: null, optionalParts: [], step: 2 };
+ case 'SET_ARM_CHOICE':
+ return { ...state, armChoice: action.payload };
case 'TOGGLE_OPTIONAL_PART': {
const id = action.payload;
const exists = state.optionalParts.includes(id);
@@ -105,7 +108,7 @@ function reducer(state, action) {
export default function Wizard({ pricing, onLogout }) {
const [state, dispatch] = useReducer(reducer, initialState);
- const { step, operator, optionalParts, groundLoops, accessControl, remoteButtons, remoteQuantity } = state;
+ const { step, operator, armChoice, optionalParts, groundLoops, accessControl, remoteButtons, remoteQuantity } = state;
const steps = [
{ num: 1, label: 'Operator' },
@@ -182,8 +185,10 @@ export default function Wizard({ pricing, onLogout }) {
dispatch({ type: 'SELECT_OPERATOR', payload: id })}
+ onSetArmChoice={(id) => dispatch({ type: 'SET_ARM_CHOICE', payload: id })}
onToggleOptionalPart={(id) => dispatch({ type: 'TOGGLE_OPTIONAL_PART', payload: id })}
onNext={() => dispatch({ type: 'NEXT_STEP' })}
/>
@@ -223,7 +228,7 @@ export default function Wizard({ pricing, onLogout }) {
{step === 4 && (
dispatch({ type: 'PREV_STEP' })}
onNew={() => dispatch({ type: 'RESET' })}
/>
diff --git a/client/src/utils/quoteCalculator.js b/client/src/utils/quoteCalculator.js
index 94cc91d..e24b0a3 100644
--- a/client/src/utils/quoteCalculator.js
+++ b/client/src/utils/quoteCalculator.js
@@ -1,5 +1,5 @@
export function calculateQuote(pricing, selections) {
- const { operator, optionalParts, groundLoops, accessControl } = selections;
+ const { operator, armChoice, optionalParts, groundLoops, accessControl } = selections;
const items = [];
let subtotal = 0;
@@ -32,28 +32,41 @@ export function calculateQuote(pricing, selections) {
});
subtotal += lineTotal;
});
- }
- }
- // Optional add-on parts for selected operator
- if (optionalParts && optionalParts.length > 0) {
- const op = typeof operator === 'string' && pricing.operators.find((o) => o.id === operator);
- if (op?.optionalParts) {
- optionalParts.forEach((partId) => {
- const part = op.optionalParts.find((p) => p.id === partId);
- if (part) {
- const lineTotal = part.qty * part.unitPrice;
+ // Selected arm option (barrier operators)
+ if (armChoice && op.armOptions) {
+ const arm = op.armOptions.find((a) => a.id === armChoice);
+ if (arm) {
items.push({
- type: 'optionalPart',
- category: 'Optional Add-Ons',
- name: part.name,
- qty: part.qty,
- unitPrice: part.unitPrice,
- lineTotal,
+ type: 'armOption',
+ category: 'Required Equipment',
+ name: arm.name,
+ qty: 1,
+ unitPrice: arm.price,
+ lineTotal: arm.price,
});
- subtotal += lineTotal;
+ subtotal += arm.price;
}
- });
+ }
+
+ // Optional add-on parts
+ if (optionalParts && optionalParts.length > 0 && op.optionalParts) {
+ optionalParts.forEach((partId) => {
+ const part = op.optionalParts.find((p) => p.id === partId);
+ if (part) {
+ const lineTotal = part.qty * part.unitPrice;
+ items.push({
+ type: 'optionalPart',
+ category: 'Optional Add-Ons',
+ name: part.name,
+ qty: part.qty,
+ unitPrice: part.unitPrice,
+ lineTotal,
+ });
+ subtotal += lineTotal;
+ }
+ });
+ }
}
}
}
diff --git a/server/data/pricing.json b/server/data/pricing.json
index 6879862..db8cf53 100644
--- a/server/data/pricing.json
+++ b/server/data/pricing.json
@@ -85,8 +85,11 @@
"image": "barrier",
"imageFile": "Mat.jpeg",
"requiredParts": [
- { "id": "op-unit-barrier", "name": "Mega Arm Tower Barrier Gate Operator(MAT)", "qty": 1, "unitPrice": 3195 },
- { "id": "barrier-arm", "name": "Barrier Arm (8ft Aluminum)", "qty": 1, "unitPrice": 395 }
+ { "id": "op-unit-barrier", "name": "Mega Arm Tower Barrier Gate Operator (MAT)", "qty": 1, "unitPrice": 3195 }
+ ],
+ "armOptions": [
+ { "id": "arm-mat-12", "name": "Barrier Arm (12ft Aluminum)", "price": 495 },
+ { "id": "arm-mat-17", "name": "Barrier Arm (17ft Aluminum)", "price": 695 }
]
},
{
@@ -99,8 +102,11 @@
"image": "barrier",
"imageFile": "techno.jpeg",
"requiredParts": [
- { "id": "op-unit-barrier-hd", "name": "Techna Barrier Gate Operator (CBG24DC)", "qty": 1, "unitPrice": 5495 },
- { "id": "barrier-arm-hd", "name": "Barrier Arm (16ft Steel)", "qty": 1, "unitPrice": 695 }
+ { "id": "op-unit-barrier-hd", "name": "Techna Barrier Gate Operator (CBG24DC)", "qty": 1, "unitPrice": 5495 }
+ ],
+ "armOptions": [
+ { "id": "arm-techna-12", "name": "Barrier Arm (12ft Steel)", "price": 595 },
+ { "id": "arm-techna-14", "name": "Barrier Arm (14ft Steel)", "price": 695 }
]
},
{