Add customer info step (name + project) between access control and quote

This commit is contained in:
Todd
2026-05-25 22:11:44 -04:00
parent b696c5054d
commit f65d1555d2
3 changed files with 111 additions and 5 deletions

View File

@ -4,6 +4,7 @@ import { calculateQuote } from '../utils/quoteCalculator';
export default function QuoteSummary({ pricing, selections, onBack, onNew }) {
const quoteRef = useRef(null);
const quote = calculateQuote(pricing, selections);
const { customerInfo } = selections;
const operator =
selections.operator && typeof selections.operator === 'object'
? selections.operator
@ -78,6 +79,16 @@ export default function QuoteSummary({ pricing, selections, onBack, onNew }) {
<p className="text-sm text-gray-500">
Quotation Date: {new Date().toLocaleDateString('en-CA')}
</p>
{customerInfo?.name && (
<p className="text-sm text-gray-700 mt-1.5 font-medium">
{customerInfo.name}
</p>
)}
{customerInfo?.project && (
<p className="text-sm text-gray-500">
{customerInfo.project}
</p>
)}
</div>
<div className="text-right">
<div className="text-lg font-bold text-blue-600">

View File

@ -0,0 +1,81 @@
import { useState, useEffect } from 'react';
export default function StepCustomerInfo({ value, onChange, onBack, onNext }) {
const [name, setName] = useState(value.name || '');
const [project, setProject] = useState(value.project || '');
useEffect(() => {
setName(value.name || '');
setProject(value.project || '');
}, [value.name, value.project]);
const handleContinue = () => {
onChange({ name: name.trim(), project: project.trim() });
onNext();
};
return (
<div>
<div className="flex items-center gap-2 mb-6">
<button
onClick={onBack}
className="p-1.5 rounded-lg hover:bg-gray-100 text-gray-500 transition-colors"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</button>
<div>
<h2 className="text-xl font-semibold text-gray-900">
Step 4: Customer Information
</h2>
<p className="text-gray-500">
Enter the customer and project details for this quote
</p>
</div>
</div>
<div className="space-y-5 max-w-lg">
<div>
<label htmlFor="customerName" className="block text-sm font-medium text-gray-700 mb-1.5">
Customer Name
</label>
<input
id="customerName"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="e.g. John Smith or ACME Corp"
className="w-full px-4 py-2.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all placeholder-gray-400"
/>
</div>
<div>
<label htmlFor="projectName" className="block text-sm font-medium text-gray-700 mb-1.5">
Project / Site Name
</label>
<input
id="projectName"
type="text"
value={project}
onChange={(e) => setProject(e.target.value)}
placeholder="e.g. Main Street Office Park"
className="w-full px-4 py-2.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition-all placeholder-gray-400"
/>
</div>
</div>
<div className="flex items-center justify-between pt-6 border-t border-gray-200 mt-8">
<div className="text-sm text-gray-500">
{name || project ? 'Information entered' : 'Optional — you can leave these blank'}
</div>
<button
onClick={handleContinue}
className="px-6 py-2.5 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 transition-colors"
>
Generate Quote
</button>
</div>
</div>
);
}

View File

@ -2,6 +2,7 @@ import { useReducer } from 'react';
import StepOperator from './StepOperator';
import StepGroundLoops from './StepGroundLoops';
import StepAccessControl from './StepAccessControl';
import StepCustomerInfo from './StepCustomerInfo';
import QuoteSummary from './QuoteSummary';
const initialState = {
@ -18,6 +19,7 @@ const initialState = {
accessControl: [],
remoteButtons: 4,
remoteQuantity: 1,
customerInfo: { name: '', project: '' },
};
function reducer(state, action) {
@ -82,8 +84,10 @@ function reducer(state, action) {
return { ...state, remoteButtons: action.payload };
case 'SET_REMOTE_QUANTITY':
return { ...state, remoteQuantity: Math.max(1, action.payload) };
case 'SET_CUSTOMER_INFO':
return { ...state, customerInfo: action.payload };
case 'NEXT_STEP':
return { ...state, step: Math.min(state.step + 1, 4) };
return { ...state, step: Math.min(state.step + 1, 5) };
case 'PREV_STEP':
return { ...state, step: Math.max(state.step - 1, 1) };
case 'SET_GROUND_LOOP_SIZE':
@ -98,7 +102,7 @@ function reducer(state, action) {
},
};
case 'GO_TO_STEP':
return { ...state, step: Math.min(action.payload, 4) };
return { ...state, step: Math.min(action.payload, 5) };
case 'RESET':
return { ...initialState };
default:
@ -108,13 +112,14 @@ function reducer(state, action) {
export default function Wizard({ pricing, onLogout }) {
const [state, dispatch] = useReducer(reducer, initialState);
const { step, operator, armChoice, optionalParts, groundLoops, accessControl, remoteButtons, remoteQuantity } = state;
const { step, operator, armChoice, optionalParts, groundLoops, accessControl, remoteButtons, remoteQuantity, customerInfo } = state;
const steps = [
{ num: 1, label: 'Operator' },
{ num: 2, label: 'Ground Loops' },
{ num: 3, label: 'Access Control' },
{ num: 4, label: 'Quote' },
{ num: 4, label: 'Customer' },
{ num: 5, label: 'Quote' },
];
return (
@ -226,9 +231,18 @@ export default function Wizard({ pricing, onLogout }) {
)}
{step === 4 && (
<StepCustomerInfo
value={customerInfo}
onChange={(info) => dispatch({ type: 'SET_CUSTOMER_INFO', payload: info })}
onBack={() => dispatch({ type: 'PREV_STEP' })}
onNext={() => dispatch({ type: 'NEXT_STEP' })}
/>
)}
{step === 5 && (
<QuoteSummary
pricing={pricing}
selections={{ operator, armChoice, optionalParts, groundLoops, accessControl, remoteButtons, remoteQuantity }}
selections={{ operator, armChoice, optionalParts, groundLoops, accessControl, remoteButtons, remoteQuantity, customerInfo }}
onBack={() => dispatch({ type: 'PREV_STEP' })}
onNew={() => dispatch({ type: 'RESET' })}
/>