shoc-frontend-new/src/components/common/address-map-preview.tsx
2026-09-14 12:31:15 -03:00

99 lines
3 KiB
TypeScript

import { Box } from "@mui/material";
import { Text } from "@/components/ui/text";
import {
buildAddressQuery,
hasCompleteAddress,
mapsEmbedUrl,
type AddressParts,
} from "@/lib/address/vendor-address";
type AddressMapPreviewProps = {
/** 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;
/**
* Render a compact accessible placeholder while the structured address is
* incomplete, instead of nothing. Meant for Add/Edit forms, where the empty
* state tells the dispatcher which parts the preview is still waiting on.
*/
placeholderWhenIncomplete?: boolean;
};
const INCOMPLETE_PLACEHOLDER_MESSAGE =
"Map preview appears once Street, City, and State are all filled.";
/**
* Small inline map for a structured address.
*
* Uses Google's keyless `output=embed` form deliberately: SH-271 asks for a
* preview with no API key, so there is no Maps JavaScript API, no Places
* request and no billing account behind this.
*
* Structured input 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. Add/Edit forms opt into a compact placeholder
* for that incomplete state; a pre-composed saved line is map-only and renders
* only when non-empty.
*/
export function AddressMapPreview({
address,
query: rawQuery,
height = 160,
placeholderWhenIncomplete = false,
}: 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 complete = address ? hasCompleteAddress(address) : Boolean(query);
if (!complete) {
if (!(address != null && placeholderWhenIncomplete)) return null;
return (
<Box
role="status"
sx={{
borderRadius: 1,
border: "1px dashed",
borderColor: "divider",
minHeight: 56,
display: "flex",
alignItems: "center",
justifyContent: "center",
px: 1.5,
py: 1,
}}
>
<Text variant="caption" tone="muted" sx={{ textAlign: "center" }}>
{INCOMPLETE_PLACEHOLDER_MESSAGE}
</Text>
</Box>
);
}
return (
<Box
sx={{
borderRadius: 1,
overflow: "hidden",
border: 1,
borderColor: "divider",
lineHeight: 0,
}}
>
<Box
component="iframe"
title={`Map of ${query}`}
src={mapsEmbedUrl({ street: query })}
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
sx={{ display: "block", width: "100%", height, border: 0 }}
/>
</Box>
);
}