89 lines
3.4 KiB
JavaScript
89 lines
3.4 KiB
JavaScript
import { useState } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
|
|
import AddressAutocomplete from '@/components/doormile/AddressAutocomplete';
|
|
|
|
// ==============================|| Doormile AI — the address turn ||============================== //
|
|
//
|
|
// The address step, as a real search field rather than a line typed into the
|
|
// chat box.
|
|
//
|
|
// It used to be an ordinary `text` step: the assistant asked for "the full
|
|
// address" and the operator typed one into the composer, which was then
|
|
// geocoded blind. Two things were wrong with that. They got no suggestions, so
|
|
// an area or street had to be remembered and spelled the way the geocoder
|
|
// wanted; and when it missed, all that came back was "I couldn't find that
|
|
// address", with no sight of what it HAD found.
|
|
//
|
|
// This is the same `AddressAutocomplete` the Create Order page uses, so the
|
|
// two surfaces resolve an address the same way and cannot drift.
|
|
//
|
|
// Picking from the list also hands the flow the coordinates the operator
|
|
// actually chose, rather than whatever a second geocode of the same words
|
|
// returns — see the `place` branch in orderFlow's resolve. That matters: the
|
|
// dispatch optimiser routes on coordinates, and re-geocoding a label can
|
|
// legitimately return a different place.
|
|
//
|
|
// Typing free text and pressing the composer still works. This is an extra way
|
|
// in, not a replacement for one.
|
|
const AIAddressStep = ({ step, isBusy, onChoose, onCancel }) => {
|
|
const [text, setText] = useState('');
|
|
const [picked, setPicked] = useState(null);
|
|
|
|
return (
|
|
<div className="w-full border border-slate-200/90 rounded-2xl bg-white p-3.5 sm:p-4 mb-3.5 shadow-3xs space-y-3">
|
|
<div className="text-xs font-bold text-slate-800">{step.ask}</div>
|
|
|
|
<AddressAutocomplete
|
|
id={`ai-address-${step.id}`}
|
|
value={text}
|
|
disabled={isBusy}
|
|
onChange={(v) => {
|
|
setText(v);
|
|
// Editing after a pick invalidates it. Those coordinates belonged to
|
|
// the old selection and must not ride along with new words — that is
|
|
// how a booking ends up located somewhere nobody chose.
|
|
setPicked(null);
|
|
}}
|
|
onPlaceSelected={(place) => {
|
|
setPicked(place);
|
|
setText(place?.formatted_address || place?.name || '');
|
|
}}
|
|
/>
|
|
|
|
{picked && (
|
|
<div className="text-[11px] text-emerald-700 font-medium">
|
|
Location set — {picked.city || picked.suburb || 'coordinates captured'}
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
type="button"
|
|
disabled={isBusy || !text.trim()}
|
|
onClick={() => onChoose(text.trim(), picked ? { place: picked } : undefined)}
|
|
className="px-4 py-2 bg-brand hover:bg-brand-dark text-white font-semibold text-xs rounded-xl shadow-xs active:scale-95 disabled:opacity-40 disabled:cursor-not-allowed transition-all cursor-pointer"
|
|
>
|
|
Continue
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={onCancel}
|
|
className="px-3 py-2 text-slate-500 hover:text-slate-800 text-xs font-medium cursor-pointer"
|
|
>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
AIAddressStep.propTypes = {
|
|
step: PropTypes.shape({ id: PropTypes.string, ask: PropTypes.string }).isRequired,
|
|
isBusy: PropTypes.bool,
|
|
onChoose: PropTypes.func.isRequired,
|
|
onCancel: PropTypes.func.isRequired
|
|
};
|
|
|
|
export default AIAddressStep;
|