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 } ] }, {