mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-04 13:42:09 +00:00
SH-331 requires Street Address suggestions after 3 typed characters, with a pick filling Street, City and State and unpicked typing kept as free text. SH-272, which SH-331 consolidates, specifies those suggestions as mocked, and the shared AddressAutocompleteField that Vendors already uses (SH-271) was built for Sites to reuse. No Places key is involved. The Add Site modal and the detail panel's edit mode now render that field. A pick writes all three values through their own controllers, so the form marks them dirty and Save changes enables in edit mode. The field gains an opt-in requiredMarker prop so Sites keeps its label asterisk while Vendors stays without one.
94 lines
3.2 KiB
TypeScript
94 lines
3.2 KiB
TypeScript
import { Autocomplete, TextField } from "@mui/material";
|
|
import {
|
|
suggestAddresses,
|
|
type AddressParts,
|
|
type AddressSuggestion,
|
|
} from "@/lib/address/vendor-address";
|
|
|
|
type AddressAutocompleteFieldProps = {
|
|
value: string;
|
|
onInputChange: (value: string) => void;
|
|
/** Fired only when a candidate row is chosen, never while typing. */
|
|
onSelect: (parts: AddressParts) => void;
|
|
label?: string;
|
|
placeholder?: string;
|
|
required?: boolean;
|
|
/**
|
|
* Show MUI's required asterisk on the label. Sites marks required fields that
|
|
* way; Vendors spells "(required)" in the label and asserts no asterisk.
|
|
*/
|
|
requiredMarker?: boolean;
|
|
error?: boolean;
|
|
helperText?: string;
|
|
disabled?: boolean;
|
|
};
|
|
|
|
/**
|
|
* Street Address input with mocked City/State completion.
|
|
*
|
|
* `freeSolo` is the whole point: typing without picking a row is a valid way to
|
|
* finish the field. The candidates are a convenience for filling City and State
|
|
* in one action, not a constraint on what the address may be.
|
|
*
|
|
* Shared rather than local to Vendors because Sites needs the identical control
|
|
* when it is built (SH-273).
|
|
*/
|
|
export function AddressAutocompleteField({
|
|
value,
|
|
onInputChange,
|
|
onSelect,
|
|
label = "Street Address",
|
|
placeholder = "Start typing the street address…",
|
|
required = false,
|
|
requiredMarker = false,
|
|
error = false,
|
|
helperText,
|
|
disabled = false,
|
|
}: AddressAutocompleteFieldProps) {
|
|
return (
|
|
<Autocomplete<AddressSuggestion, false, false, true>
|
|
freeSolo
|
|
disabled={disabled}
|
|
options={suggestAddresses(value)}
|
|
// The options are already derived from the input; letting MUI filter them
|
|
// again would drop every row whose label does not literally contain the
|
|
// typed text.
|
|
filterOptions={(options) => options}
|
|
inputValue={value}
|
|
onInputChange={(_event, next, reason) => {
|
|
// Genuine typing and the clear control update the street. MUI also
|
|
// fires this callback with reason "reset" after a selection, carrying
|
|
// the option's full "<street>, <city>, <state>" label — forwarding
|
|
// that would overwrite the just-picked street with the label.
|
|
if (reason !== "input" && reason !== "clear") return;
|
|
onInputChange(next);
|
|
}}
|
|
onChange={(_event, selected) => {
|
|
if (selected && typeof selected !== "string") {
|
|
onSelect({ street: selected.street, city: selected.city, state: selected.state });
|
|
return;
|
|
}
|
|
|
|
if (selected === null) onSelect({ street: "", city: "", state: "" });
|
|
}}
|
|
getOptionLabel={(option) => (typeof option === "string" ? option : option.label)}
|
|
renderInput={(params) => (
|
|
<TextField
|
|
{...params}
|
|
label={label}
|
|
placeholder={placeholder}
|
|
error={error}
|
|
helperText={helperText}
|
|
fullWidth
|
|
required={requiredMarker}
|
|
// Required is set on the input itself; MUI's `required` prop only
|
|
// adds the label asterisk, which Vendors must not render.
|
|
slotProps={{
|
|
...params.slotProps,
|
|
htmlInput: { ...params.slotProps?.htmlInput, required },
|
|
}}
|
|
/>
|
|
)}
|
|
/>
|
|
);
|
|
}
|