shoc-frontend-new/src/components/common/address-autocomplete-field.tsx

95 lines
3.2 KiB
TypeScript
Raw Normal View History

feat(vendors): structure the address into Street/City/State with autocomplete (#176) * feat(vendors): structure the address into Street/City/State with autocomplete The Vendor form carried one free-text "Address (optional)" line, with City, State and Zip already present but hidden behind display:none, and a separate "Google Maps URL" input someone had to paste into by hand. Street Address, City and State are now three required fields. Typing three characters in Street Address offers up to four candidates; picking one fills all three at once, and typing without picking stays plain free text. The Google Maps URL input is gone — the location is derived from the address, the Street Address itself is the link in view mode, and a keyless map preview renders once all three parts are present. Zip stays in the payload but out of the form; the ticket scopes the visible set to three. The suggestion algorithm, candidate cities and copy are ported from the approved prototype rather than invented, so dev and design agree on what a dispatcher sees. Suggestions are deterministic for a given input on purpose: a reshuffling list moves a row out from under the pointer mid-click. Test fixtures that predate the requirement now carry an address, so each test still fails for the reason it is about. The two drawer tests asserting the stored-URL "Open in Google Maps" row are rewritten to the behaviour that replaced it. Delivers SH-271. * feat(work-orders): show the vendor location map in the Vendor dialog The last of SH-271's five acceptance bullets. The Work Order Vendor dialog gets one composed address line from the vendor dropdown payload, not the structured parts the form and detail drawer hold, so the preview takes the line directly — it is saved data either way, and the completeness rule exists to stop a map of half-typed input, not to reject a stored address. The dialog's hand-built maps URL now goes through the shared helper, so the link and the preview cannot drift apart. * test(vendors): update the browser specs and pixel baselines for the new address `npm run verify` does not run Playwright, so the first push went out with the browser suite still asserting the UI this ticket removes. Three assertions were stale: the combined "Address (optional)" input, the "Google Maps URL (optional)" input, and the detail drawer's separate "Open in Google Maps" row — now replaced by the Street Address itself being the link, checked against the derived href. A fourth test created a vendor without an address, which the new requirement blocks; it fills one, so the test still fails only for the reason it is about. Baselines regenerated in mcr.microsoft.com/playwright:v1.61.1-noble, the image CI uses — macOS font rendering produces different pixels. Exactly three of the sixteen were rewritten (vendor add, edit, detail); the rest, including every Work Orders shot, are byte-identical, which is the evidence that this change stays inside the surfaces it claims. * test(vendors): keep the map preview out of the pixel baselines The visual suite mocks `**/api/**` and nothing else, so the address map preview's iframe reached maps.google.com for real. Whether that frame paints, and what it paints, depends on the network and on what Google serves that minute — which is why `vendor-edit` failed in CI at 18178 differing pixels while passing in a container that could not reach Google. Regenerating the baseline would not have fixed it; it would have moved the flake. Aborting the request pins the frame to a blank box, so the shot measures our layout and nothing else. The committed baselines are unchanged by this — they were already correct — and a second container run with no --update passes 16/16, which is the evidence the shot is now stable rather than merely green once. * fix(vendors): complete structured address map flows * test(vendors): align add visual baseline with CI --------- Co-authored-by: Codex Review Integration <codex-review@local.invalid>
2026-09-15 16:13:15 -03:00
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;
feat(vendors): structure the address into Street/City/State with autocomplete (#176) * feat(vendors): structure the address into Street/City/State with autocomplete The Vendor form carried one free-text "Address (optional)" line, with City, State and Zip already present but hidden behind display:none, and a separate "Google Maps URL" input someone had to paste into by hand. Street Address, City and State are now three required fields. Typing three characters in Street Address offers up to four candidates; picking one fills all three at once, and typing without picking stays plain free text. The Google Maps URL input is gone — the location is derived from the address, the Street Address itself is the link in view mode, and a keyless map preview renders once all three parts are present. Zip stays in the payload but out of the form; the ticket scopes the visible set to three. The suggestion algorithm, candidate cities and copy are ported from the approved prototype rather than invented, so dev and design agree on what a dispatcher sees. Suggestions are deterministic for a given input on purpose: a reshuffling list moves a row out from under the pointer mid-click. Test fixtures that predate the requirement now carry an address, so each test still fails for the reason it is about. The two drawer tests asserting the stored-URL "Open in Google Maps" row are rewritten to the behaviour that replaced it. Delivers SH-271. * feat(work-orders): show the vendor location map in the Vendor dialog The last of SH-271's five acceptance bullets. The Work Order Vendor dialog gets one composed address line from the vendor dropdown payload, not the structured parts the form and detail drawer hold, so the preview takes the line directly — it is saved data either way, and the completeness rule exists to stop a map of half-typed input, not to reject a stored address. The dialog's hand-built maps URL now goes through the shared helper, so the link and the preview cannot drift apart. * test(vendors): update the browser specs and pixel baselines for the new address `npm run verify` does not run Playwright, so the first push went out with the browser suite still asserting the UI this ticket removes. Three assertions were stale: the combined "Address (optional)" input, the "Google Maps URL (optional)" input, and the detail drawer's separate "Open in Google Maps" row — now replaced by the Street Address itself being the link, checked against the derived href. A fourth test created a vendor without an address, which the new requirement blocks; it fills one, so the test still fails only for the reason it is about. Baselines regenerated in mcr.microsoft.com/playwright:v1.61.1-noble, the image CI uses — macOS font rendering produces different pixels. Exactly three of the sixteen were rewritten (vendor add, edit, detail); the rest, including every Work Orders shot, are byte-identical, which is the evidence that this change stays inside the surfaces it claims. * test(vendors): keep the map preview out of the pixel baselines The visual suite mocks `**/api/**` and nothing else, so the address map preview's iframe reached maps.google.com for real. Whether that frame paints, and what it paints, depends on the network and on what Google serves that minute — which is why `vendor-edit` failed in CI at 18178 differing pixels while passing in a container that could not reach Google. Regenerating the baseline would not have fixed it; it would have moved the flake. Aborting the request pins the frame to a blank box, so the shot measures our layout and nothing else. The committed baselines are unchanged by this — they were already correct — and a second container run with no --update passes 16/16, which is the evidence the shot is now stable rather than merely green once. * fix(vendors): complete structured address map flows * test(vendors): align add visual baseline with CI --------- Co-authored-by: Codex Review Integration <codex-review@local.invalid>
2026-09-15 16:13:15 -03:00
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,
feat(vendors): structure the address into Street/City/State with autocomplete (#176) * feat(vendors): structure the address into Street/City/State with autocomplete The Vendor form carried one free-text "Address (optional)" line, with City, State and Zip already present but hidden behind display:none, and a separate "Google Maps URL" input someone had to paste into by hand. Street Address, City and State are now three required fields. Typing three characters in Street Address offers up to four candidates; picking one fills all three at once, and typing without picking stays plain free text. The Google Maps URL input is gone — the location is derived from the address, the Street Address itself is the link in view mode, and a keyless map preview renders once all three parts are present. Zip stays in the payload but out of the form; the ticket scopes the visible set to three. The suggestion algorithm, candidate cities and copy are ported from the approved prototype rather than invented, so dev and design agree on what a dispatcher sees. Suggestions are deterministic for a given input on purpose: a reshuffling list moves a row out from under the pointer mid-click. Test fixtures that predate the requirement now carry an address, so each test still fails for the reason it is about. The two drawer tests asserting the stored-URL "Open in Google Maps" row are rewritten to the behaviour that replaced it. Delivers SH-271. * feat(work-orders): show the vendor location map in the Vendor dialog The last of SH-271's five acceptance bullets. The Work Order Vendor dialog gets one composed address line from the vendor dropdown payload, not the structured parts the form and detail drawer hold, so the preview takes the line directly — it is saved data either way, and the completeness rule exists to stop a map of half-typed input, not to reject a stored address. The dialog's hand-built maps URL now goes through the shared helper, so the link and the preview cannot drift apart. * test(vendors): update the browser specs and pixel baselines for the new address `npm run verify` does not run Playwright, so the first push went out with the browser suite still asserting the UI this ticket removes. Three assertions were stale: the combined "Address (optional)" input, the "Google Maps URL (optional)" input, and the detail drawer's separate "Open in Google Maps" row — now replaced by the Street Address itself being the link, checked against the derived href. A fourth test created a vendor without an address, which the new requirement blocks; it fills one, so the test still fails only for the reason it is about. Baselines regenerated in mcr.microsoft.com/playwright:v1.61.1-noble, the image CI uses — macOS font rendering produces different pixels. Exactly three of the sixteen were rewritten (vendor add, edit, detail); the rest, including every Work Orders shot, are byte-identical, which is the evidence that this change stays inside the surfaces it claims. * test(vendors): keep the map preview out of the pixel baselines The visual suite mocks `**/api/**` and nothing else, so the address map preview's iframe reached maps.google.com for real. Whether that frame paints, and what it paints, depends on the network and on what Google serves that minute — which is why `vendor-edit` failed in CI at 18178 differing pixels while passing in a container that could not reach Google. Regenerating the baseline would not have fixed it; it would have moved the flake. Aborting the request pins the frame to a blank box, so the shot measures our layout and nothing else. The committed baselines are unchanged by this — they were already correct — and a second container run with no --update passes 16/16, which is the evidence the shot is now stable rather than merely green once. * fix(vendors): complete structured address map flows * test(vendors): align add visual baseline with CI --------- Co-authored-by: Codex Review Integration <codex-review@local.invalid>
2026-09-15 16:13:15 -03:00
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.
feat(vendors): structure the address into Street/City/State with autocomplete (#176) * feat(vendors): structure the address into Street/City/State with autocomplete The Vendor form carried one free-text "Address (optional)" line, with City, State and Zip already present but hidden behind display:none, and a separate "Google Maps URL" input someone had to paste into by hand. Street Address, City and State are now three required fields. Typing three characters in Street Address offers up to four candidates; picking one fills all three at once, and typing without picking stays plain free text. The Google Maps URL input is gone — the location is derived from the address, the Street Address itself is the link in view mode, and a keyless map preview renders once all three parts are present. Zip stays in the payload but out of the form; the ticket scopes the visible set to three. The suggestion algorithm, candidate cities and copy are ported from the approved prototype rather than invented, so dev and design agree on what a dispatcher sees. Suggestions are deterministic for a given input on purpose: a reshuffling list moves a row out from under the pointer mid-click. Test fixtures that predate the requirement now carry an address, so each test still fails for the reason it is about. The two drawer tests asserting the stored-URL "Open in Google Maps" row are rewritten to the behaviour that replaced it. Delivers SH-271. * feat(work-orders): show the vendor location map in the Vendor dialog The last of SH-271's five acceptance bullets. The Work Order Vendor dialog gets one composed address line from the vendor dropdown payload, not the structured parts the form and detail drawer hold, so the preview takes the line directly — it is saved data either way, and the completeness rule exists to stop a map of half-typed input, not to reject a stored address. The dialog's hand-built maps URL now goes through the shared helper, so the link and the preview cannot drift apart. * test(vendors): update the browser specs and pixel baselines for the new address `npm run verify` does not run Playwright, so the first push went out with the browser suite still asserting the UI this ticket removes. Three assertions were stale: the combined "Address (optional)" input, the "Google Maps URL (optional)" input, and the detail drawer's separate "Open in Google Maps" row — now replaced by the Street Address itself being the link, checked against the derived href. A fourth test created a vendor without an address, which the new requirement blocks; it fills one, so the test still fails only for the reason it is about. Baselines regenerated in mcr.microsoft.com/playwright:v1.61.1-noble, the image CI uses — macOS font rendering produces different pixels. Exactly three of the sixteen were rewritten (vendor add, edit, detail); the rest, including every Work Orders shot, are byte-identical, which is the evidence that this change stays inside the surfaces it claims. * test(vendors): keep the map preview out of the pixel baselines The visual suite mocks `**/api/**` and nothing else, so the address map preview's iframe reached maps.google.com for real. Whether that frame paints, and what it paints, depends on the network and on what Google serves that minute — which is why `vendor-edit` failed in CI at 18178 differing pixels while passing in a container that could not reach Google. Regenerating the baseline would not have fixed it; it would have moved the flake. Aborting the request pins the frame to a blank box, so the shot measures our layout and nothing else. The committed baselines are unchanged by this — they were already correct — and a second container run with no --update passes 16/16, which is the evidence the shot is now stable rather than merely green once. * fix(vendors): complete structured address map flows * test(vendors): align add visual baseline with CI --------- Co-authored-by: Codex Review Integration <codex-review@local.invalid>
2026-09-15 16:13:15 -03:00
slotProps={{
...params.slotProps,
htmlInput: { ...params.slotProps?.htmlInput, required },
}}
/>
)}
/>
);
}