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.
This commit is contained in:
Codex Review Integration 2026-09-10 16:36:09 -03:00
parent aa707cb370
commit 2844d97a36
2 changed files with 38 additions and 15 deletions

View file

@ -1,4 +1,6 @@
import { ExternalLink, Mail, MapPin } from "lucide-react";
import { AddressMapPreview } from "@/components/common/address-map-preview";
import { mapsSearchUrl } from "@/lib/address/vendor-address";
import { Detail } from "@/app/(protected)/workorders/_components/wizard/wizard-labels";
import type { VendorDropdownItem } from "@/domain/work-orders/types/work-order";
import { formatVendorDistance } from "@/domain/work-orders/utils/vendor-dropdown-label";
@ -33,15 +35,23 @@ export function VendorDialogCompanyCard({ company, vendor }: VendorDialogCompany
{Boolean(vendor?.address) && <Detail icon={MapPin} text={vendor?.address ?? ""} />}
{Boolean(email) && <Detail icon={Mail} text={email} />}
{vendor != null && Boolean(vendor.address) && (
<a
href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(vendor.address)}`}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-1.5 text-[12px] hover:underline"
style={{ color: "var(--color-primary)" }}
>
<ExternalLink size={12} /> Open in Google Maps
</a>
<>
<a
href={mapsSearchUrl({ street: vendor.address })}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-1.5 text-[12px] hover:underline"
style={{ color: "var(--color-primary)" }}
>
<ExternalLink size={12} /> Open in Google Maps
</a>
{/*
SH-271: the vendor dropdown payload carries one composed address
line, not the structured parts the form and detail drawer hold, so
the preview is given the line directly. It is saved data either way.
*/}
<AddressMapPreview query={vendor.address} height={140} />
</>
)}
</div>
);

View file

@ -7,7 +7,14 @@ import {
} from "@/lib/address/vendor-address";
type AddressMapPreviewProps = {
address: Partial<AddressParts>;
/** Structured parts, as held by a form or a detail view. */
address?: Partial<AddressParts>;
/**
* An already-composed address line, for callers that hold saved data rather
* than live input — the Work Order vendor dialog gets one string from the
* vendor dropdown payload. Ignored when `address` is given.
*/
query?: string;
height?: number;
};
@ -21,10 +28,16 @@ type AddressMapPreviewProps = {
* Renders nothing until Street, City and State are all present — a preview of a
* half-typed address points somewhere misleading, which is worse than no map.
*/
export function AddressMapPreview({ address, height = 160 }: AddressMapPreviewProps) {
if (!hasCompleteAddress(address)) return null;
const query = buildAddressQuery(address);
export function AddressMapPreview({
address,
query: rawQuery,
height = 160,
}: AddressMapPreviewProps) {
// Structured input is held to the completeness rule; a pre-composed line is
// saved data and only has to be non-empty.
const query = address ? buildAddressQuery(address) : (rawQuery ?? "").trim();
const renderable = address ? hasCompleteAddress(address) : Boolean(query);
if (!renderable) return null;
return (
<Box
sx={{
@ -38,7 +51,7 @@ export function AddressMapPreview({ address, height = 160 }: AddressMapPreviewPr
<Box
component="iframe"
title={`Map of ${query}`}
src={mapsEmbedUrl(address)}
src={mapsEmbedUrl({ street: query })}
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
sx={{ display: "block", width: "100%", height, border: 0 }}