Add customer info step (name + project) between access control and quote
This commit is contained in:
@ -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">
|
||||
|
||||
81
client/src/components/StepCustomerInfo.jsx
Normal file
81
client/src/components/StepCustomerInfo.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@ -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' })}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user